From ad26530c0f136a18f252859d1ae5e716b27182c8 Mon Sep 17 00:00:00 2001 From: Stephan Vedder Date: Tue, 29 Apr 2025 17:23:24 +0200 Subject: [PATCH 1/4] [ZH][CMAKE] Separate AudioFileCache from MilesAudioManager --- .../Code/GameEngineDevice/CMakeLists.txt | 2 + .../MilesAudioDevice/MilesAudioManager.h | 80 +---- .../MilesAudioDevice/MilesAudioFileCache.cpp | 279 ++++++++++++++++++ .../MilesAudioDevice/MilesAudioFileCache.h | 96 ++++++ .../MilesAudioDevice/MilesAudioManager.cpp | 235 +-------------- 5 files changed, 383 insertions(+), 309 deletions(-) create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.cpp create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index c604974471..1f29343b0a 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -90,6 +90,8 @@ set(GAMEENGINEDEVICE_SRC Include/Win32Device/GameClient/Win32DIKeyboard.h Include/Win32Device/GameClient/Win32DIMouse.h Include/Win32Device/GameClient/Win32Mouse.h + Source/MilesAudioDevice/MilesAudioFileCache.h + Source/MilesAudioDevice/MilesAudioFileCache.cpp Source/MilesAudioDevice/MilesAudioManager.cpp Source/VideoDevice/Bink/BinkVideoPlayer.cpp Source/W3DDevice/Common/System/W3DFunctionLexicon.cpp diff --git a/GeneralsMD/Code/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h b/GeneralsMD/Code/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h index c483abf085..3856c92ff5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h @@ -19,7 +19,7 @@ // FILE: MilesAudioManager.h ////////////////////////////////////////////////////////////////////////// // MilesAudioManager implementation // Author: John K. McDonald, July 2002 - +#pragma once #include "Common/AsciiString.h" #include "Common/GameAudio.h" #include "mss/mss.h" @@ -51,33 +51,6 @@ enum PlayingWhich CPP_11(: Int) PW_INVALID }; -struct PlayingAudio -{ - union - { - HSAMPLE m_sample; - H3DSAMPLE m_3DSample; - HSTREAM m_stream; - }; - - PlayingAudioType m_type; - volatile PlayingStatus m_status; // This member is adjusted by another running thread. - AudioEventRTS *m_audioEventRTS; - void *m_file; // The file that was opened to play this - Bool m_requestStop; - Bool m_cleanupAudioEventRTS; - Int m_framesFaded; - - PlayingAudio() : - m_type(PAT_INVALID), - m_audioEventRTS(NULL), - m_requestStop(false), - m_cleanupAudioEventRTS(true), - m_sample(NULL), - m_framesFaded(0) - { } -}; - struct ProviderInfo { AsciiString name; @@ -85,53 +58,8 @@ struct ProviderInfo Bool m_isValid; }; -struct OpenAudioFile -{ - AILSOUNDINFO m_soundInfo; - void *m_file; - UnsignedInt m_openCount; - UnsignedInt m_fileSize; - - Bool m_compressed; // if the file was compressed, then we need to free it with a miles function. - - // Note: OpenAudioFile does not own this m_eventInfo, and should not delete it. - const AudioEventInfo *m_eventInfo; // Not mutable, unlike the one on AudioEventRTS. -}; - -typedef std::hash_map< AsciiString, OpenAudioFile, rts::hash, rts::equal_to > OpenFilesHash; -typedef OpenFilesHash::iterator OpenFilesHashIt; - -class AudioFileCache -{ - public: - AudioFileCache(); - - // Protected by mutex - virtual ~AudioFileCache(); - void *openFile( AudioEventRTS *eventToOpenFrom ); - void closeFile( void *fileToClose ); - void setMaxSize( UnsignedInt size ); - // End Protected by mutex - - // Note: These functions should be used for informational purposes only. For speed reasons, - // they are not protected by the mutex, so they are not guarenteed to be valid if called from - // outside the audio cache. They should be used as a rough estimate only. - UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; } - UnsignedInt getMaxSize() const { return m_maxSize; } - - protected: - void releaseOpenAudioFile( OpenAudioFile *fileToRelease ); - - // This function will return TRUE if it was able to free enough space, and FALSE otherwise. - Bool freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace); - - OpenFilesHash m_openFiles; - UnsignedInt m_currentlyUsedSize; - UnsignedInt m_maxSize; - HANDLE m_mutex; - const char *m_mutexName; -}; - +struct PlayingAudio; +class MilesAudioFileCache; class MilesAudioManager : public AudioManager { @@ -315,7 +243,7 @@ class MilesAudioManager : public AudioManager // in the sound engine std::list m_stoppedAudio; - AudioFileCache *m_audioCache; + MilesAudioFileCache *m_audioCache; PlayingAudio *m_binkHandle; UnsignedInt m_num2DSamples; UnsignedInt m_num3DSamples; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.cpp b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.cpp new file mode 100644 index 0000000000..2f5821c4a9 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.cpp @@ -0,0 +1,279 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MilesAudioFileCache.cpp +/*---------------------------------------------------------------------------*/ +/* EA Pacific */ +/* Confidential Information */ +/* Copyright (C) 2001 - All Rights Reserved */ +/* DO NOT DISTRIBUTE */ +/*---------------------------------------------------------------------------*/ +/* Project: RTS3 */ +/* File name: MilesAudioFileCache.cpp */ +/* Created: John K. McDonald, Jr., 3/21/2002 */ +/* Desc: This is the implementation for the MilesAudioManager, which */ +/* interfaces with the Miles Sound System. */ +/* Revision History: */ +/* 7/18/2002 : Initial creation */ +/* 4/29/2025 : Moved AudioFileCache into a separate file and renamed */ +/*---------------------------------------------------------------------------*/ + +#include "MilesAudioFileCache.h" + +#include "Common/AudioEventInfo.h" +#include "Common/AudioEventRTS.h" +#include "Common/File.h" +#include "Common/FileSystem.h" +#include "Common/ScopedMutex.h" + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +MilesAudioFileCache::MilesAudioFileCache() : m_maxSize(0), m_currentlyUsedSize(0), m_mutexName("AudioFileCacheMutex") +{ + m_mutex = CreateMutex(NULL, FALSE, m_mutexName); +} + +//------------------------------------------------------------------------------------------------- +MilesAudioFileCache::~MilesAudioFileCache() +{ + { + ScopedMutex mut(m_mutex); + + // Free all the samples that are open. + OpenFilesHashIt it; + for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) { + if (it->second.m_openCount > 0) { + DEBUG_CRASH(("Sample '%s' is still playing, and we're trying to quit.\n", it->second.m_eventInfo->m_audioName.str())); + } + + releaseOpenAudioFile(&it->second); + // Don't erase it from the map, cause it makes this whole process way more complicated, and + // we're about to go away anyways. + } + } + + CloseHandle(m_mutex); +} + +//------------------------------------------------------------------------------------------------- +void *MilesAudioFileCache::openFile( AudioEventRTS *eventToOpenFrom ) +{ + // Protect the entire openFile function + ScopedMutex mut(m_mutex); + + AsciiString strToFind; + switch (eventToOpenFrom->getNextPlayPortion()) + { + case PP_Attack: + strToFind = eventToOpenFrom->getAttackFilename(); + break; + case PP_Sound: + strToFind = eventToOpenFrom->getFilename(); + break; + case PP_Decay: + strToFind = eventToOpenFrom->getDecayFilename(); + break; + case PP_Done: + return NULL; + } + + OpenFilesHash::iterator it; + it = m_openFiles.find(strToFind); + + if (it != m_openFiles.end()) { + ++it->second.m_openCount; + return it->second.m_file; + } + + // Couldn't find the file, so actually open it. + File *file = TheFileSystem->openFile(strToFind.str()); + if (!file) { + DEBUG_ASSERTLOG(strToFind.isEmpty(), ("Missing Audio File: '%s'\n", strToFind.str())); + return NULL; + } + + UnsignedInt fileSize = file->size(); + char* buffer = file->readEntireAndClose(); + + OpenAudioFile openedAudioFile; + openedAudioFile.m_eventInfo = eventToOpenFrom->getAudioEventInfo(); + + AILSOUNDINFO soundInfo; + AIL_WAV_info(buffer, &soundInfo); + + if (eventToOpenFrom->isPositionalAudio()) { + if (soundInfo.channels > 1) { + DEBUG_CRASH(("Requested Positional Play of audio '%s', but it is in stereo.", strToFind.str())); + delete [] buffer; + return NULL; + } + } + + if (soundInfo.format == WAVE_FORMAT_IMA_ADPCM) { + void *decompressFileBuffer; + U32 newFileSize; + AIL_decompress_ADPCM(&soundInfo, &decompressFileBuffer, &newFileSize); + fileSize = newFileSize; + openedAudioFile.m_compressed = TRUE; + delete [] buffer; + openedAudioFile.m_file = decompressFileBuffer; + openedAudioFile.m_soundInfo = soundInfo; + openedAudioFile.m_openCount = 1; + } else if (soundInfo.format == WAVE_FORMAT_PCM) { + openedAudioFile.m_compressed = FALSE; + openedAudioFile.m_file = buffer; + openedAudioFile.m_soundInfo = soundInfo; + openedAudioFile.m_openCount = 1; + } else { + DEBUG_CRASH(("Unexpected compression type in '%s'\n", strToFind.str())); + // prevent leaks + delete [] buffer; + return NULL; + } + + openedAudioFile.m_fileSize = fileSize; + m_currentlyUsedSize += openedAudioFile.m_fileSize; + if (m_currentlyUsedSize > m_maxSize) { + // We need to free some samples, or we're not going to be able to play this sound. + if (!freeEnoughSpaceForSample(openedAudioFile)) { + m_currentlyUsedSize -= openedAudioFile.m_fileSize; + releaseOpenAudioFile(&openedAudioFile); + return NULL; + } + } + + m_openFiles[strToFind] = openedAudioFile; + return openedAudioFile.m_file; +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioFileCache::closeFile( void *fileToClose ) +{ + if (!fileToClose) { + return; + } + + // Protect the entire closeFile function + ScopedMutex mut(m_mutex); + + OpenFilesHash::iterator it; + for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) { + if ( it->second.m_file == fileToClose ) { + --it->second.m_openCount; + return; + } + } +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioFileCache::setMaxSize( UnsignedInt size ) +{ + // Protect the function, in case we're trying to use this value elsewhere. + ScopedMutex mut(m_mutex); + + m_maxSize = size; +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioFileCache::releaseOpenAudioFile( OpenAudioFile *fileToRelease ) +{ + if (fileToRelease->m_openCount > 0) { + // This thing needs to be terminated IMMEDIATELY. + TheAudio->closeAnySamplesUsingFile(fileToRelease->m_file); + } + + if (fileToRelease->m_file) { + if (fileToRelease->m_compressed) { + // Files read in via AIL_decompress_ADPCM must be freed with AIL_mem_free_lock. + AIL_mem_free_lock(fileToRelease->m_file); + } else { + // Otherwise, we read it, we own it, blow it away. + delete [] fileToRelease->m_file; + } + fileToRelease->m_file = NULL; + fileToRelease->m_eventInfo = NULL; + } +} + +//------------------------------------------------------------------------------------------------- +Bool MilesAudioFileCache::freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace) +{ + + Int spaceRequired = m_currentlyUsedSize - m_maxSize; + Int runningTotal = 0; + + std::list filesToClose; + // First, search for any samples that have ref counts of 0. They are low-hanging fruit, and + // should be considered immediately. + OpenFilesHashIt it; + for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_openCount == 0) { + // This is said low-hanging fruit. + filesToClose.push_back(it->first); + + runningTotal += it->second.m_fileSize; + + if (runningTotal >= spaceRequired) { + break; + } + } + } + + // If we don't have enough space yet, then search through the events who have a count of 1 or more + // and who are lower priority than this sound. + // Mical said that at this point, sounds shouldn't care if other sounds are interruptable or not. + // Kill any files of lower priority necessary to clear our the buffer. + if (runningTotal < spaceRequired) { + for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_openCount > 0) { + if (it->second.m_eventInfo->m_priority < sampleThatNeedsSpace.m_eventInfo->m_priority) { + filesToClose.push_back(it->first); + runningTotal += it->second.m_fileSize; + + if (runningTotal >= spaceRequired) { + break; + } + } + } + } + } + + // We weren't able to find enough sounds to truncate. Therefore, this sound is not going to play. + if (runningTotal < spaceRequired) { + return FALSE; + } + + std::list::iterator ait; + for (ait = filesToClose.begin(); ait != filesToClose.end(); ++ait) { + OpenFilesHashIt itToErase = m_openFiles.find(*ait); + if (itToErase != m_openFiles.end()) { + releaseOpenAudioFile(&itToErase->second); + m_currentlyUsedSize -= itToErase->second.m_fileSize; + m_openFiles.erase(itToErase); + } + } + + return TRUE; +} \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h new file mode 100644 index 0000000000..c6af7c464e --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h @@ -0,0 +1,96 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: MilesAudioFileCache.h ////////////////////////////////////////////////////////////////////////// +// MilesAudioManager implementation +// Author: John K. McDonald, July 2002 +#include "MilesAudioDevice/MilesAudioManager.h" + +struct PlayingAudio +{ + union + { + HSAMPLE m_sample; + H3DSAMPLE m_3DSample; + HSTREAM m_stream; + }; + + PlayingAudioType m_type; + volatile PlayingStatus m_status; // This member is adjusted by another running thread. + AudioEventRTS *m_audioEventRTS; + void *m_file; // The file that was opened to play this + Bool m_requestStop; + Bool m_cleanupAudioEventRTS; + Int m_framesFaded; + + PlayingAudio() : + m_type(PAT_INVALID), + m_audioEventRTS(NULL), + m_requestStop(false), + m_cleanupAudioEventRTS(true), + m_sample(NULL), + m_framesFaded(0) + { } +}; + +struct OpenAudioFile +{ + AILSOUNDINFO m_soundInfo; + void *m_file; + UnsignedInt m_openCount; + UnsignedInt m_fileSize; + + Bool m_compressed; // if the file was compressed, then we need to free it with a miles function. + + // Note: OpenAudioFile does not own this m_eventInfo, and should not delete it. + const AudioEventInfo *m_eventInfo; // Not mutable, unlike the one on AudioEventRTS. +}; + +typedef std::hash_map< AsciiString, OpenAudioFile, rts::hash, rts::equal_to > OpenFilesHash; +typedef OpenFilesHash::iterator OpenFilesHashIt; + +class MilesAudioFileCache +{ + public: + MilesAudioFileCache(); + + // Protected by mutex + virtual ~MilesAudioFileCache(); + void *openFile( AudioEventRTS *eventToOpenFrom ); + void closeFile( void *fileToClose ); + void setMaxSize( UnsignedInt size ); + // End Protected by mutex + + // Note: These functions should be used for informational purposes only. For speed reasons, + // they are not protected by the mutex, so they are not guarenteed to be valid if called from + // outside the audio cache. They should be used as a rough estimate only. + UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; } + UnsignedInt getMaxSize() const { return m_maxSize; } + + protected: + void releaseOpenAudioFile( OpenAudioFile *fileToRelease ); + + // This function will return TRUE if it was able to free enough space, and FALSE otherwise. + Bool freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace); + + OpenFilesHash m_openFiles; + UnsignedInt m_currentlyUsedSize; + UnsignedInt m_maxSize; + HANDLE m_mutex; + const char *m_mutexName; +}; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index 02a07ffe2d..0246bc8760 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -41,6 +41,7 @@ #include #include "Lib/BaseType.h" #include "MilesAudioDevice/MilesAudioManager.h" +#include "MilesAudioFileCache.h" #include "Common/AudioAffect.h" #include "Common/AudioHandleSpecialValues.h" @@ -98,7 +99,7 @@ MilesAudioManager::MilesAudioManager() : m_pref3DProvider(AsciiString::TheEmptyString), m_prefSpeaker(AsciiString::TheEmptyString) { - m_audioCache = NEW AudioFileCache; + m_audioCache = NEW MilesAudioFileCache; } //------------------------------------------------------------------------------------------------- @@ -3092,238 +3093,6 @@ U32 AILCALLBACK streamingFileRead(U32 file_handle, void *buffer, U32 bytes) return ((File*) file_handle)->read(buffer, bytes); } -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AudioFileCache::AudioFileCache() : m_maxSize(0), m_currentlyUsedSize(0), m_mutexName("AudioFileCacheMutex") -{ - m_mutex = CreateMutex(NULL, FALSE, m_mutexName); -} - -//------------------------------------------------------------------------------------------------- -AudioFileCache::~AudioFileCache() -{ - { - ScopedMutex mut(m_mutex); - - // Free all the samples that are open. - OpenFilesHashIt it; - for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) { - if (it->second.m_openCount > 0) { - DEBUG_CRASH(("Sample '%s' is still playing, and we're trying to quit.\n", it->second.m_eventInfo->m_audioName.str())); - } - - releaseOpenAudioFile(&it->second); - // Don't erase it from the map, cause it makes this whole process way more complicated, and - // we're about to go away anyways. - } - } - - CloseHandle(m_mutex); -} - -//------------------------------------------------------------------------------------------------- -void *AudioFileCache::openFile( AudioEventRTS *eventToOpenFrom ) -{ - // Protect the entire openFile function - ScopedMutex mut(m_mutex); - - AsciiString strToFind; - switch (eventToOpenFrom->getNextPlayPortion()) - { - case PP_Attack: - strToFind = eventToOpenFrom->getAttackFilename(); - break; - case PP_Sound: - strToFind = eventToOpenFrom->getFilename(); - break; - case PP_Decay: - strToFind = eventToOpenFrom->getDecayFilename(); - break; - case PP_Done: - return NULL; - } - - OpenFilesHash::iterator it; - it = m_openFiles.find(strToFind); - - if (it != m_openFiles.end()) { - ++it->second.m_openCount; - return it->second.m_file; - } - - // Couldn't find the file, so actually open it. - File *file = TheFileSystem->openFile(strToFind.str()); - if (!file) { - DEBUG_ASSERTLOG(strToFind.isEmpty(), ("Missing Audio File: '%s'\n", strToFind.str())); - return NULL; - } - - UnsignedInt fileSize = file->size(); - char* buffer = file->readEntireAndClose(); - - OpenAudioFile openedAudioFile; - openedAudioFile.m_eventInfo = eventToOpenFrom->getAudioEventInfo(); - - AILSOUNDINFO soundInfo; - AIL_WAV_info(buffer, &soundInfo); - - if (eventToOpenFrom->isPositionalAudio()) { - if (soundInfo.channels > 1) { - DEBUG_CRASH(("Requested Positional Play of audio '%s', but it is in stereo.", strToFind.str())); - delete [] buffer; - return NULL; - } - } - - if (soundInfo.format == WAVE_FORMAT_IMA_ADPCM) { - void *decompressFileBuffer; - U32 newFileSize; - AIL_decompress_ADPCM(&soundInfo, &decompressFileBuffer, &newFileSize); - fileSize = newFileSize; - openedAudioFile.m_compressed = TRUE; - delete [] buffer; - openedAudioFile.m_file = decompressFileBuffer; - openedAudioFile.m_soundInfo = soundInfo; - openedAudioFile.m_openCount = 1; - } else if (soundInfo.format == WAVE_FORMAT_PCM) { - openedAudioFile.m_compressed = FALSE; - openedAudioFile.m_file = buffer; - openedAudioFile.m_soundInfo = soundInfo; - openedAudioFile.m_openCount = 1; - } else { - DEBUG_CRASH(("Unexpected compression type in '%s'\n", strToFind.str())); - // prevent leaks - delete [] buffer; - return NULL; - } - - openedAudioFile.m_fileSize = fileSize; - m_currentlyUsedSize += openedAudioFile.m_fileSize; - if (m_currentlyUsedSize > m_maxSize) { - // We need to free some samples, or we're not going to be able to play this sound. - if (!freeEnoughSpaceForSample(openedAudioFile)) { - m_currentlyUsedSize -= openedAudioFile.m_fileSize; - releaseOpenAudioFile(&openedAudioFile); - return NULL; - } - } - - m_openFiles[strToFind] = openedAudioFile; - return openedAudioFile.m_file; -} - -//------------------------------------------------------------------------------------------------- -void AudioFileCache::closeFile( void *fileToClose ) -{ - if (!fileToClose) { - return; - } - - // Protect the entire closeFile function - ScopedMutex mut(m_mutex); - - OpenFilesHash::iterator it; - for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) { - if ( it->second.m_file == fileToClose ) { - --it->second.m_openCount; - return; - } - } -} - -//------------------------------------------------------------------------------------------------- -void AudioFileCache::setMaxSize( UnsignedInt size ) -{ - // Protect the function, in case we're trying to use this value elsewhere. - ScopedMutex mut(m_mutex); - - m_maxSize = size; -} - -//------------------------------------------------------------------------------------------------- -void AudioFileCache::releaseOpenAudioFile( OpenAudioFile *fileToRelease ) -{ - if (fileToRelease->m_openCount > 0) { - // This thing needs to be terminated IMMEDIATELY. - TheAudio->closeAnySamplesUsingFile(fileToRelease->m_file); - } - - if (fileToRelease->m_file) { - if (fileToRelease->m_compressed) { - // Files read in via AIL_decompress_ADPCM must be freed with AIL_mem_free_lock. - AIL_mem_free_lock(fileToRelease->m_file); - } else { - // Otherwise, we read it, we own it, blow it away. - delete [] fileToRelease->m_file; - } - fileToRelease->m_file = NULL; - fileToRelease->m_eventInfo = NULL; - } -} - -//------------------------------------------------------------------------------------------------- -Bool AudioFileCache::freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace) -{ - - Int spaceRequired = m_currentlyUsedSize - m_maxSize; - Int runningTotal = 0; - - std::list filesToClose; - // First, search for any samples that have ref counts of 0. They are low-hanging fruit, and - // should be considered immediately. - OpenFilesHashIt it; - for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { - if (it->second.m_openCount == 0) { - // This is said low-hanging fruit. - filesToClose.push_back(it->first); - - runningTotal += it->second.m_fileSize; - - if (runningTotal >= spaceRequired) { - break; - } - } - } - - // If we don't have enough space yet, then search through the events who have a count of 1 or more - // and who are lower priority than this sound. - // Mical said that at this point, sounds shouldn't care if other sounds are interruptable or not. - // Kill any files of lower priority necessary to clear our the buffer. - if (runningTotal < spaceRequired) { - for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { - if (it->second.m_openCount > 0) { - if (it->second.m_eventInfo->m_priority < sampleThatNeedsSpace.m_eventInfo->m_priority) { - filesToClose.push_back(it->first); - runningTotal += it->second.m_fileSize; - - if (runningTotal >= spaceRequired) { - break; - } - } - } - } - } - - // We weren't able to find enough sounds to truncate. Therefore, this sound is not going to play. - if (runningTotal < spaceRequired) { - return FALSE; - } - - std::list::iterator ait; - for (ait = filesToClose.begin(); ait != filesToClose.end(); ++ait) { - OpenFilesHashIt itToErase = m_openFiles.find(*ait); - if (itToErase != m_openFiles.end()) { - releaseOpenAudioFile(&itToErase->second); - m_currentlyUsedSize -= itToErase->second.m_fileSize; - m_openFiles.erase(itToErase); - } - } - - return TRUE; -} - - #if defined(RTS_DEBUG) || defined(RTS_INTERNAL) //------------------------------------------------------------------------------------------------- void MilesAudioManager::dumpAllAssetsUsed() From 5ddfeb108993638409f21b3ddfb581af79417b21 Mon Sep 17 00:00:00 2001 From: Stephan Vedder Date: Tue, 29 Apr 2025 17:24:46 +0200 Subject: [PATCH 2/4] [CMAKE] Add OpenAL build option --- .../Code/GameEngineDevice/CMakeLists.txt | 18 ++++++++++++++++++ .../MilesAudioDevice/MilesAudioFileCache.h | 18 ++++++++++-------- cmake/config-build.cmake | 2 ++ vcpkg.json | 3 ++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 1f29343b0a..20ff5adfed 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -247,4 +247,22 @@ if(RTS_BUILD_OPTION_FFMPEG) target_link_libraries(z_gameenginedevice PRIVATE ${FFMPEG_LIBRARIES}) target_compile_definitions(z_gameenginedevice PUBLIC RTS_HAS_FFMPEG) endif() +endif() + +if(RTS_BUILD_OPTION_OPENAL) + find_package(OpenAL CONFIG REQUIRED) + + if(OpenAL_FOUND) + #target_sources(z_gameenginedevice PRIVATE + # Include/OpenALDevice/OpenALAudioManager.h + # Include/OpenALDevice/OpenALAudioStream.h + # Source/OpenALDevice/OpenALAudioCache.h + # Source/OpenALDevice/OpenALAudioCache.cpp + # Source/OpenALDevice/OpenALAudioManager.cpp + # Source/OpenALDevice/OpenALAudioStream.cpp + #) + + target_link_libraries(z_gameenginedevice PRIVATE OpenAL::OpenAL) + target_compile_definitions(z_gameenginedevice PUBLIC RTS_HAS_OPENAL) + endif() endif() \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h index c6af7c464e..fc6c646361 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h +++ b/GeneralsMD/Code/GameEngineDevice/Source/MilesAudioDevice/MilesAudioFileCache.h @@ -19,6 +19,8 @@ // FILE: MilesAudioFileCache.h ////////////////////////////////////////////////////////////////////////// // MilesAudioManager implementation // Author: John K. McDonald, July 2002 +#pragma once + #include "MilesAudioDevice/MilesAudioManager.h" struct PlayingAudio @@ -37,11 +39,11 @@ struct PlayingAudio Bool m_requestStop; Bool m_cleanupAudioEventRTS; Int m_framesFaded; - - PlayingAudio() : - m_type(PAT_INVALID), - m_audioEventRTS(NULL), - m_requestStop(false), + + PlayingAudio() : + m_type(PAT_INVALID), + m_audioEventRTS(NULL), + m_requestStop(false), m_cleanupAudioEventRTS(true), m_sample(NULL), m_framesFaded(0) @@ -56,7 +58,7 @@ struct OpenAudioFile UnsignedInt m_fileSize; Bool m_compressed; // if the file was compressed, then we need to free it with a miles function. - + // Note: OpenAudioFile does not own this m_eventInfo, and should not delete it. const AudioEventInfo *m_eventInfo; // Not mutable, unlike the one on AudioEventRTS. }; @@ -68,7 +70,7 @@ class MilesAudioFileCache { public: MilesAudioFileCache(); - + // Protected by mutex virtual ~MilesAudioFileCache(); void *openFile( AudioEventRTS *eventToOpenFrom ); @@ -87,7 +89,7 @@ class MilesAudioFileCache // This function will return TRUE if it was able to free enough space, and FALSE otherwise. Bool freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace); - + OpenFilesHash m_openFiles; UnsignedInt m_currentlyUsedSize; UnsignedInt m_maxSize; diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index a001e42c59..c19df0c3ef 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -8,6 +8,7 @@ option(RTS_BUILD_OPTION_PROFILE "Build code with the \"Profile\" configuration." option(RTS_BUILD_OPTION_DEBUG "Build code with the \"Debug\" configuration." OFF) option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) +option(RTS_BUILD_OPTION_OPENAL "Enable OpenAL support" OFF) if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) set(RTS_BUILD_ZEROHOUR TRUE) @@ -23,6 +24,7 @@ add_feature_info(ProfileBuild RTS_BUILD_OPTION_PROFILE "Building as a \"Profile\ add_feature_info(DebugBuild RTS_BUILD_OPTION_DEBUG "Building as a \"Debug\" build") add_feature_info(AddressSanitizer RTS_BUILD_OPTION_ASAN "Building with address sanitizer") add_feature_info(FFmpegSupport RTS_BUILD_OPTION_FFMPEG "Building with FFmpeg support") +add_feature_info(OpenALSupport RTS_BUILD_OPTION_OPENAL "Building with OpenAL support") if(RTS_BUILD_ZEROHOUR) option(RTS_BUILD_ZEROHOUR_TOOLS "Build tools for Zero Hour" ON) diff --git a/vcpkg.json b/vcpkg.json index 011b913c8a..39fa3f71c7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", "dependencies": [ "zlib", - "ffmpeg" + "ffmpeg", + "openal-soft" ] } \ No newline at end of file From eb62dd988694212bcc49f4213643970b4573f902 Mon Sep 17 00:00:00 2001 From: Stephan Vedder Date: Tue, 29 Apr 2025 23:57:43 +0200 Subject: [PATCH 3/4] [LINUX][ZH] Add OpenAL AudioManager implementation --- .../Code/GameEngineDevice/CMakeLists.txt | 12 +- .../Include/OpenALDevice/OpenALAudioManager.h | 253 ++ .../Win32Device/Common/Win32GameEngine.h | 7 + .../OpenALDevice/OpenALAudioFileCache.cpp | 325 ++ .../OpenALDevice/OpenALAudioFileCache.h | 138 + .../OpenALDevice/OpenALAudioManager.cpp | 3069 +++++++++++++++++ .../VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp | 12 +- 7 files changed, 3804 insertions(+), 12 deletions(-) create mode 100644 GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioManager.h create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.cpp create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.h create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 20ff5adfed..921a61e8cb 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -253,14 +253,14 @@ if(RTS_BUILD_OPTION_OPENAL) find_package(OpenAL CONFIG REQUIRED) if(OpenAL_FOUND) - #target_sources(z_gameenginedevice PRIVATE - # Include/OpenALDevice/OpenALAudioManager.h + target_sources(z_gameenginedevice PRIVATE + Include/OpenALDevice/OpenALAudioManager.h # Include/OpenALDevice/OpenALAudioStream.h - # Source/OpenALDevice/OpenALAudioCache.h - # Source/OpenALDevice/OpenALAudioCache.cpp - # Source/OpenALDevice/OpenALAudioManager.cpp + Source/OpenALDevice/OpenALAudioFileCache.h + Source/OpenALDevice/OpenALAudioFileCache.cpp + Source/OpenALDevice/OpenALAudioManager.cpp # Source/OpenALDevice/OpenALAudioStream.cpp - #) + ) target_link_libraries(z_gameenginedevice PRIVATE OpenAL::OpenAL) target_compile_definitions(z_gameenginedevice PUBLIC RTS_HAS_OPENAL) diff --git a/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioManager.h b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioManager.h new file mode 100644 index 0000000000..3f896cf3fd --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioManager.h @@ -0,0 +1,253 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: OpenALAudioManager.h ////////////////////////////////////////////////////////////////////////// +// OpenALAudioManager implementation +// Author: Stephan Vedder, April 2025 +#pragma once +#include "Common/AsciiString.h" +#include "Common/GameAudio.h" +#include +#include + +class AudioEventRTS; + +enum { MAXPROVIDERS = 64 }; + +#define AL_MAX_PLAYBACK_DEVICES 64 + +enum PlayingAudioType CPP_11(: Int) +{ + PAT_Sample, + PAT_3DSample, + PAT_Stream, + PAT_INVALID +}; + +enum PlayingStatus CPP_11(: Int) +{ + PS_Playing, + PS_Stopped, + PS_Paused +}; + +enum PlayingWhich CPP_11(: Int) +{ + PW_Attack, + PW_Sound, + PW_Decay, + PW_INVALID +}; + +struct ProviderInfo +{ + AsciiString name; + Bool m_isValid; +}; + +class PlayingAudio; +class OpenALAudioFileCache; +class OpenALAudioStream; +class OpenALAudioManager : public AudioManager +{ + friend class OpenALAudioStream; + friend class FFmpegVideoStream; + friend class OpenALAudioFileCache; + public: +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + virtual void audioDebugDisplay(DebugDisplayInterface *dd, void *, FILE *fp = NULL ); + virtual AudioHandle addAudioEvent( const AudioEventRTS *eventToAdd ); ///< Add an audio event (event must be declared in an INI file) +#endif + + // from AudioDevice + virtual void init(); + virtual void postProcessLoad(); + virtual void reset(); + virtual void update(); + + OpenALAudioManager(); + virtual ~OpenALAudioManager(); + + + virtual void nextMusicTrack( void ); + virtual void prevMusicTrack( void ); + virtual Bool isMusicPlaying( void ) const; + virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const; + virtual AsciiString getMusicTrackName( void ) const; + + virtual void openDevice( void ); + virtual void closeDevice( void ); + virtual void *getDevice( void ) { return m_alcDevice; } + + virtual void stopAudio( AudioAffect which ); + virtual void pauseAudio( AudioAffect which ); + virtual void resumeAudio( AudioAffect which ); + virtual void pauseAmbient( Bool shouldPause ); + + virtual void killAudioEventImmediately( AudioHandle audioEvent ); + + ///< Return whether the current audio is playing or not. + ///< NOTE NOTE NOTE !!DO NOT USE THIS IN FOR GAMELOGIC PURPOSES!! NOTE NOTE NOTE + virtual Bool isCurrentlyPlaying( AudioHandle handle ); + + virtual void notifyOfAudioCompletion( ALuint source, UnsignedInt flags ); + virtual PlayingAudio *findPlayingAudioFrom( ALuint source, UnsignedInt flags ); + + virtual UnsignedInt getProviderCount( void ) const; + virtual AsciiString getProviderName( UnsignedInt providerNum ) const; + virtual UnsignedInt getProviderIndex( AsciiString providerName ) const; + virtual void selectProvider( UnsignedInt providerNdx ); + virtual void unselectProvider( void ); + virtual UnsignedInt getSelectedProvider( void ) const; + virtual void setSpeakerType( UnsignedInt speakerType ); + virtual UnsignedInt getSpeakerType( void ); + + virtual void *getHandleForBink( void ); + virtual void releaseHandleForBink( void ); + + virtual void friend_forcePlayAudioEventRTS(const AudioEventRTS* eventToPlay); + + virtual UnsignedInt getNum2DSamples( void ) const; + virtual UnsignedInt getNum3DSamples( void ) const; + virtual UnsignedInt getNumStreams( void ) const; + + virtual Bool doesViolateLimit( AudioEventRTS *event ) const; + virtual Bool isPlayingLowerPriority( AudioEventRTS *event ) const; + virtual Bool isPlayingAlready( AudioEventRTS *event ) const; + virtual Bool isObjectPlayingVoice( UnsignedInt objID ) const; + Bool killLowestPrioritySoundImmediately( AudioEventRTS *event ); + AudioEventRTS* findLowestPrioritySound( AudioEventRTS *event ); + + virtual void adjustVolumeOfPlayingAudio(AsciiString eventName, Real newVolume); + + virtual void removePlayingAudio( AsciiString eventName ); + virtual void removeAllDisabledAudio(); + + virtual void processRequestList( void ); + virtual void processPlayingList( void ); + virtual void processFadingList( void ); + virtual void processStoppedList( void ); + + Bool shouldProcessRequestThisFrame( AudioRequest *req ) const; + void adjustRequest( AudioRequest *req ); + Bool checkForSample( AudioRequest *req ); + + virtual void setHardwareAccelerated(Bool accel); + virtual void setSpeakerSurround(Bool surround); + + virtual void setPreferredProvider(AsciiString provider) { m_pref3DProvider = provider; } + virtual void setPreferredSpeaker(AsciiString speakerType) { m_prefSpeaker = speakerType; } + + virtual Real getFileLengthMS( AsciiString strToLoad ) const; + + virtual void closeAnySamplesUsingFile( const void *fileToClose ); + + + virtual Bool has3DSensitiveStreamsPlaying( void ) const; + + + protected: + // 3-D functions + virtual void setDeviceListenerPosition( void ); + const Coord3D *getCurrentPositionFromEvent( AudioEventRTS *event ); + Bool isOnScreen( const Coord3D *pos ) const; + Real getEffectiveVolume(AudioEventRTS *event) const; + + // Looping functions + Bool startNextLoop( PlayingAudio *looping ); + + void playStream( AudioEventRTS *event, OpenALAudioStream* stream ); + // Returns the buffer handle representing audio data for attachment to the PlayingAudio structure + ALuint playSample( AudioEventRTS *event, PlayingAudio *audio ); + ALuint playSample3D( AudioEventRTS *event, PlayingAudio * audio ); + + protected: + void enumerateDevices( void ); + void createListener( void ); + void initDelayFilter( void ); + Bool isValidProvider( void ); + void initSamplePools( void ); + void processRequest( AudioRequest *req ); + + void playAudioEvent( AudioEventRTS *event ); + void stopAudioEvent( AudioHandle handle ); + void pauseAudioEvent( AudioHandle handle ); + + ALuint loadBufferForRead( AudioEventRTS *eventToLoadFrom ); + void closeBuffer( ALuint bufferToClose ); + + PlayingAudio *allocatePlayingAudio( void ); + void releaseOpenALHandles( PlayingAudio *release ); + void releasePlayingAudio( PlayingAudio *release ); + + void stopAllAudioImmediately( void ); + void freeAllOpenALHandles( void ); + + PlayingAudio *getFirst2DSample( AudioEventRTS *event ); + PlayingAudio *getFirst3DSample( AudioEventRTS *event ); + + void adjustPlayingVolume( PlayingAudio *audio ); + + void stopAllSpeech( void ); + static ALenum getALFormat( uint8_t channels, uint8_t bitsPerSample ); + protected: + ProviderInfo m_provider3D[MAXPROVIDERS]; + UnsignedInt m_providerCount; + UnsignedInt m_selectedProvider; + UnsignedInt m_lastProvider; + UnsignedInt m_selectedSpeakerType; + + AsciiString m_pref3DProvider; + AsciiString m_prefSpeaker; + + // Currently Playing stuff. Useful if we have to preempt it. + // This should rarely if ever happen, as we mirror this in Sounds, and attempt to + // keep preemption from taking place here. + std::list m_playingSounds; + std::list m_playing3DSounds; + std::list m_playingStreams; + + // Currently fading stuff. At this point, we just want to let it finish fading, when it is + // done it should be added to the completed list, then "freed" and the counts should be updated + // on the next update + std::list m_fadingAudio; + + // Stuff that is done playing (either because it has finished or because it was killed) + // This stuff should be cleaned up during the next update cycle. This includes updating counts + // in the sound engine + std::list m_stoppedAudio; + + OpenALAudioFileCache *m_audioCache; + UnsignedInt m_num2DSamples; + UnsignedInt m_num3DSamples; + UnsignedInt m_numStreams; + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + typedef std::set SetAsciiString; + typedef SetAsciiString::iterator SetAsciiStringIt; + SetAsciiString m_allEventsLoaded; + void dumpAllAssetsUsed(); +#endif + + AsciiString m_alDevicesList[AL_MAX_PLAYBACK_DEVICES]; + int m_alMaxDevicesIndex; + ALCdevice *m_alcDevice = nullptr; + ALCcontext *m_alcContext = nullptr; + OpenALAudioStream* m_binkAudio = nullptr; +}; + diff --git a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h index e5cbd2c344..8fb8aba29e 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h @@ -39,6 +39,9 @@ #include "GameLogic/GameLogic.h" #include "GameNetwork/NetworkInterface.h" #include "MilesAudioDevice/MilesAudioManager.h" +#ifdef RTS_HAS_OPENAL +#include "OpenALDevice/OpenALAudioManager.h" +#endif #include "Win32Device/Common/Win32BIGFileSystem.h" #include "Win32Device/Common/Win32LocalFileSystem.h" #include "W3DDevice/Common/W3DModuleFactory.h" @@ -102,6 +105,10 @@ inline ParticleSystemManager* Win32GameEngine::createParticleSystemManager( void inline NetworkInterface *Win32GameEngine::createNetwork( void ) { return NetworkInterface::createNetwork(); } inline Radar *Win32GameEngine::createRadar( void ) { return NEW W3DRadar; } inline WebBrowser *Win32GameEngine::createWebBrowser( void ) { return NEW CComObject; } +#ifdef RTS_HAS_OPENAL +inline AudioManager *Win32GameEngine::createAudioManager( void ) { return NEW OpenALAudioManager; } +#else inline AudioManager *Win32GameEngine::createAudioManager( void ) { return NEW MilesAudioManager; } +#endif #endif // end __WIN32GAMEENGINE_H_ diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.cpp new file mode 100644 index 0000000000..b52ad01416 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.cpp @@ -0,0 +1,325 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: OpenALAudioFileCache.cpp ////////////////////////////////////////////////////////////////////////// +// OpenALAudioCache implementation +// Author: Stephan Vedder, April 2025 +#include "OpenALAudioFileCache.h" + +extern "C" { +#include +} + +#include "Common/AudioEventInfo.h" +#include "Common/AudioEventRTS.h" +#include "Common/File.h" +#include "Common/FileSystem.h" + +#include + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +OpenALAudioFileCache::OpenALAudioFileCache() : m_maxSize(0), m_currentlyUsedSize(0) +{ +} + +Bool OpenALAudioFileCache::decodeFFmpeg(OpenAudioFile* file) +{ + std::vector audioData; + auto on_frame = [&audioData](AVFrame* frame, int stream_idx, int stream_type, void* user_data) { + OpenAudioFile* file = static_cast(user_data); + if (stream_type != AVMEDIA_TYPE_AUDIO) { + return; + } + + const int frame_data_size = file->m_ffmpegFile->getSizeForSamples(frame->nb_samples); + audioData.reserve(audioData.size() + frame_data_size); + + if (av_sample_fmt_is_planar(static_cast(frame->format))) { + // Convert planar audio to interleaved + int num_channels = file->m_ffmpegFile->getNumChannels(); + int bytes_per_sample = file->m_ffmpegFile->getBytesPerSample(); + for (int sample = 0; sample < frame->nb_samples; ++sample) { + for (int channel = 0; channel < num_channels; ++channel) { + const uint8_t* src = frame->data[channel] + sample * bytes_per_sample; + audioData.insert(audioData.end(), src, src + bytes_per_sample); + } + } + } else { + // Directly copy interleaved audio + audioData.insert(audioData.end(), frame->data[0], frame->data[0] + frame_data_size); + } + file->m_fileSize += frame_data_size; + file->m_totalSamples += frame->nb_samples; + }; + + file->m_ffmpegFile->setFrameCallback(on_frame); + file->m_ffmpegFile->setUserData(file); + + // Read all packets inside the file + while (file->m_ffmpegFile->decodePacket()) { + } + + // Fill the buffer with the audio data + alBufferData(file->m_buffer, OpenALAudioManager::getALFormat(file->m_ffmpegFile->getNumChannels(), file->m_ffmpegFile->getBytesPerSample() * 8), + audioData.data(), audioData.size(), file->m_ffmpegFile->getSampleRate()); + + // Calculate the duration in MS + file->m_duration = (file->m_totalSamples / (float)file->m_ffmpegFile->getSampleRate()) * 1000.0f; + + return true; +} + +//------------------------------------------------------------------------------------------------- +OpenALAudioFileCache::~OpenALAudioFileCache() +{ + // Free all the samples that are open. + OpenFilesHashIt it; + for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_openCount > 0) { + DEBUG_CRASH(("Sample '%s' is still playing, and we're trying to quit.\n", it->second.m_eventInfo->m_audioName.str())); + } + + releaseOpenAudioFile(&it->second); + // Don't erase it from the map, cause it makes this whole process way more complicated, and + // we're about to go away anyways. + } +} + +//------------------------------------------------------------------------------------------------- +ALuint OpenALAudioFileCache::getBufferForFile(const OpenFileInfo &fileInfo) +{ + AudioEventRTS *eventToOpenFrom = fileInfo.event; + + AsciiString strToFind; + if (eventToOpenFrom) + { + switch (eventToOpenFrom->getNextPlayPortion()) + { + case PP_Attack: + strToFind = eventToOpenFrom->getAttackFilename(); + break; + case PP_Sound: + strToFind = eventToOpenFrom->getFilename(); + break; + case PP_Decay: + strToFind = eventToOpenFrom->getDecayFilename(); + break; + case PP_Done: + return 0; + } + } + else + { + if (fileInfo.filename) + { + strToFind = *fileInfo.filename; + } + else + { + DEBUG_CRASH(("No filename to open\n")); + return 0; + } + } + + auto it = m_openFiles.find(strToFind); + + if (it != m_openFiles.end()) { + ++it->second.m_openCount; + return it->second.m_buffer; + } + + // Couldn't find the file, so actually open it. + File* file = TheFileSystem->openFile(strToFind.str()); + if (!file) { + DEBUG_ASSERTLOG(strToFind.isEmpty(), ("Missing Audio File: '%s'\n", strToFind.str())); + return 0; + } + + UnsignedInt fileSize = file->size(); + + OpenAudioFile openedAudioFile; + alGenBuffers(1, &openedAudioFile.m_buffer); + openedAudioFile.m_eventInfo = eventToOpenFrom ? eventToOpenFrom->getAudioEventInfo() : NULL; + openedAudioFile.m_ffmpegFile = new FFmpegFile(); + + // This transfer ownership of file + if (!openedAudioFile.m_ffmpegFile->open(file)) { + releaseOpenAudioFile(&openedAudioFile); + return 0; + } + + if (eventToOpenFrom && eventToOpenFrom->isPositionalAudio()) { + if (openedAudioFile.m_ffmpegFile->getNumChannels() > 1) { + DEBUG_CRASH(("Requested Positional Play of audio '%s', but it is in stereo.", strToFind.str())); + releaseOpenAudioFile(&openedAudioFile); + return 0; + } + } + + if (!decodeFFmpeg(&openedAudioFile)) { + releaseOpenAudioFile(&openedAudioFile); + return 0; + } + + openedAudioFile.m_ffmpegFile->close(); + + openedAudioFile.m_fileSize = fileSize; + m_currentlyUsedSize += openedAudioFile.m_fileSize; + if (m_currentlyUsedSize > m_maxSize) { + // We need to free some samples, or we're not going to be able to play this sound. + if (!freeEnoughSpaceForSample(openedAudioFile)) { + m_currentlyUsedSize -= openedAudioFile.m_fileSize; + releaseOpenAudioFile(&openedAudioFile); + return 0; + } + } + + m_openFiles[strToFind] = openedAudioFile; + return openedAudioFile.m_buffer; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioFileCache::closeBuffer(ALuint bufferToClose) +{ + if (!bufferToClose) { + return; + } + + OpenFilesHash::iterator it; + for ( it = m_openFiles.begin(); it != m_openFiles.end(); ++it ) { + if ( it->second.m_buffer == bufferToClose ) { + --it->second.m_openCount; + return; + } + } +} + +float OpenALAudioFileCache::getBufferLength(ALuint handle) +{ + if (!handle) { + return 0.0f; + } + + for (auto it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_buffer == handle) { + return it->second.m_duration; + } + } + + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioFileCache::setMaxSize(UnsignedInt size) +{ + // Protect the function, in case we're trying to use this value elsewhere. + + m_maxSize = size; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioFileCache::releaseOpenAudioFile(OpenAudioFile* fileToRelease) +{ + if (fileToRelease->m_openCount > 0) { + // This thing needs to be terminated IMMEDIATELY. + TheAudio->closeAnySamplesUsingFile((const void*)(uintptr_t)fileToRelease->m_buffer); + } + + if (fileToRelease->m_ffmpegFile) { + // Free FFMPEG handles + delete fileToRelease->m_ffmpegFile; + } + + if (fileToRelease->m_buffer) + { + // Free the OpenAL buffer + alDeleteBuffers(1, &fileToRelease->m_buffer); + } + fileToRelease->m_ffmpegFile = NULL; + fileToRelease->m_buffer = 0; + fileToRelease->m_eventInfo = NULL; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioFileCache::freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace) +{ + + Int spaceRequired = m_currentlyUsedSize - m_maxSize; + Int runningTotal = 0; + + std::list filesToClose; + // First, search for any samples that have ref counts of 0. They are low-hanging fruit, and + // should be considered immediately. + OpenFilesHashIt it; + for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_openCount == 0) { + // This is said low-hanging fruit. + filesToClose.push_back(it->first); + + runningTotal += it->second.m_fileSize; + + if (runningTotal >= spaceRequired) { + break; + } + } + } + + // If we don't have enough space yet, then search through the events who have a count of 1 or more + // and who are lower priority than this sound. + // Mical said that at this point, sounds shouldn't care if other sounds are interruptable or not. + // Kill any files of lower priority necessary to clear our the buffer. + if (runningTotal < spaceRequired) { + for (it = m_openFiles.begin(); it != m_openFiles.end(); ++it) { + if (it->second.m_openCount > 0) { + if (it->second.m_eventInfo->m_priority < sampleThatNeedsSpace.m_eventInfo->m_priority) { + filesToClose.push_back(it->first); + runningTotal += it->second.m_fileSize; + + if (runningTotal >= spaceRequired) { + break; + } + } + } + } + } + + // We weren't able to find enough sounds to truncate. Therefore, this sound is not going to play. + if (runningTotal < spaceRequired) { + return FALSE; + } + + std::list::iterator ait; + for (ait = filesToClose.begin(); ait != filesToClose.end(); ++ait) { + OpenFilesHashIt itToErase = m_openFiles.find(*ait); + if (itToErase != m_openFiles.end()) { + releaseOpenAudioFile(&itToErase->second); + m_currentlyUsedSize -= itToErase->second.m_fileSize; + m_openFiles.erase(itToErase); + } + } + + return TRUE; +} \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.h b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.h new file mode 100644 index 0000000000..4803d0bc37 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioFileCache.h @@ -0,0 +1,138 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: OpenALAudioFileCache.h ////////////////////////////////////////////////////////////////////////// +// OpenALAudioCache implementation +// Author: Stephan Vedder, April 2025 +#pragma once + +#include "OpenALDevice/OpenALAudioManager.h" + +#ifndef RTS_HAS_FFMPEG +#error "RTS_HAS_FFMPEG must be defined to use the OpenALAudioFileCache." +#endif + +#include "VideoDevice/FFmpeg/FFmpegFile.h" + +#include +#include + +class OpenALAudioStream; +struct PlayingAudio +{ + union + { + ALuint m_source; + OpenALAudioStream* m_stream; + }; + + PlayingAudioType m_type; + AudioEventRTS* m_audioEventRTS; + FFmpegFile* m_ffmpegFile; + // The created OpenAL buffer handle for this file + ALuint m_bufferHandle; + Bool m_requestStop; + Bool m_cleanupAudioEventRTS; + Int m_framesFaded; + + PlayingAudio() : + m_type(PAT_INVALID), + m_audioEventRTS(NULL), + m_ffmpegFile(NULL), + m_bufferHandle(0), + m_requestStop(false), + m_cleanupAudioEventRTS(true), + m_stream(NULL), + m_framesFaded(0) + { } +}; + +struct OpenAudioFile +{ + ALuint m_buffer = 0; + FFmpegFile* m_ffmpegFile = NULL; + UnsignedInt m_openCount; + UnsignedInt m_fileSize; + UnsignedInt m_channels = 0; + UnsignedInt m_bitsPerSample = 0; + UnsignedInt m_freq = 0; + + // Note: OpenAudioFile does not own this m_eventInfo, and should not delete it. + const AudioEventInfo *m_eventInfo; // Not mutable, unlike the one on AudioEventRTS. + int m_totalSamples = 0; + float m_duration = 0.0f; +}; + +struct OpenFileInfo +{ + AsciiString* filename; + AudioEventRTS* event; + + OpenFileInfo(AsciiString *filename) : filename(filename), + event(NULL) + { + } + + OpenFileInfo(AudioEventRTS *event) : filename(NULL), + event(event) + { + } +}; + +typedef std::hash_map< AsciiString, OpenAudioFile, rts::hash, rts::equal_to > OpenFilesHash; +typedef OpenFilesHash::iterator OpenFilesHashIt; + +class OpenALAudioFileCache +{ + public: + OpenALAudioFileCache(); + + // Protected by mutex + virtual ~OpenALAudioFileCache(); + ALuint getBufferForFile(const OpenFileInfo& fileToOpenFrom); + void closeBuffer(ALuint bufferToClose); + void setMaxSize(UnsignedInt size); + float getBufferLength(ALuint handle); + // End Protected by mutex + + // Note: These functions should be used for informational purposes only. For speed reasons, + // they are not protected by the mutex, so they are not guarenteed to be valid if called from + // outside the audio cache. They should be used as a rough estimate only. + UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; } + UnsignedInt getMaxSize() const { return m_maxSize; } + + static void getWaveData(void* wave_data, + uint8_t*& data, + UnsignedInt& size, + UnsignedInt& freq, + UnsignedInt& channels, + UnsignedInt& bitsPerSample); + + protected: + void releaseOpenAudioFile(OpenAudioFile* fileToRelease); + + // This function will return TRUE if it was able to free enough space, and FALSE otherwise. + Bool freeEnoughSpaceForSample(const OpenAudioFile& sampleThatNeedsSpace); + + // FFmpeg related + Bool decodeFFmpeg(OpenAudioFile* fileToDecode); + + OpenFilesHash m_openFiles; + UnsignedInt m_currentlyUsedSize; + UnsignedInt m_maxSize; +}; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp new file mode 100644 index 0000000000..cdd362fd37 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp @@ -0,0 +1,3069 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: OpenALAudioManager.cpp +/*---------------------------------------------------------------------------*/ +/* EA Pacific */ +/* Confidential Information */ +/* Copyright (C) 2001 - All Rights Reserved */ +/* DO NOT DISTRIBUTE */ +/*---------------------------------------------------------------------------*/ +/* Project: RTS3 */ +/* File name: OpenALAudioManager.cpp */ +/* Created: Stephan Vedder, 3/9/2025s */ +/* Desc: This is the implementation for the OpenALAudioManager, which */ +/* interfaces with the Miles Sound System. */ +/* Revision History: */ +/* 3/9/2025 : Initial creation */ +/* 4/29/2025: Adjustments for SuperHackers */ +/*---------------------------------------------------------------------------*/ + +#include "Lib/BaseType.h" +#include "OpenALDevice/OpenALAudioManager.h" +//#include "OpenALAudioDevice/OpenALAudioStream.h" +#include "OpenALAudioFileCache.h" + +#include "Common/AudioAffect.h" +#include "Common/AudioHandleSpecialValues.h" +#include "Common/AudioRequest.h" +#include "Common/AudioSettings.h" +#include "Common/AsciiString.h" +#include "Common/AudioEventInfo.h" +#include "Common/FileSystem.h" +#include "Common/GameCommon.h" +#include "Common/GameSounds.h" +#include "Common/CRCDebug.h" +#include "Common/GlobalData.h" +#include "Common/ScopedMutex.h" + +#include "GameClient/DebugDisplay.h" +#include "GameClient/Drawable.h" +#include "GameClient/GameClient.h" +#include "GameClient/VideoPlayer.h" +#include "GameClient/View.h" + +#include "GameLogic/GameLogic.h" +#include "GameLogic/TerrainLogic.h" + +#include "Common/File.h" + +#include + +extern "C" { +#include +#include +} + +#ifdef RTS_INTERNAL +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +enum { INFINITE_LOOP_COUNT = 1000000 }; + +#define LOAD_ALC_PROC(N) N = reinterpret_cast(alcGetProcAddress(m_alcDevice, #N)) + +static inline bool sourceIsStopped(ALuint source) +{ + ALenum state; + alGetSourcei(source, AL_SOURCE_STATE, &state); + + return (state == AL_STOPPED); +} + +//------------------------------------------------------------------------------------------------- +OpenALAudioManager::OpenALAudioManager() : + m_providerCount(1), + m_selectedProvider(PROVIDER_ERROR), + m_selectedSpeakerType(0), + m_lastProvider(PROVIDER_ERROR), + m_alcDevice(NULL), + m_alcContext(NULL), + m_num2DSamples(0), + m_num3DSamples(0), + m_numStreams(0), + m_binkAudio(NULL), + m_pref3DProvider(AsciiString::TheEmptyString), + m_prefSpeaker(AsciiString::TheEmptyString) +{ + m_audioCache = NEW OpenALAudioFileCache; +} + +//------------------------------------------------------------------------------------------------- +OpenALAudioManager::~OpenALAudioManager() +{ + DEBUG_ASSERTCRASH(m_binkAudio == NULL, ("Leaked a Bink handle. Chuybregts")); + releaseHandleForBink(); + closeDevice(); + delete m_audioCache; + + DEBUG_ASSERTCRASH(this == TheAudio, ("Umm...\n")); + TheAudio = NULL; +} + +//------------------------------------------------------------------------------------------------- +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) +AudioHandle OpenALAudioManager::addAudioEvent(const AudioEventRTS* eventToAdd) +{ + if (TheGlobalData->m_preloadReport) { + if (!eventToAdd->getEventName().isEmpty()) { + m_allEventsLoaded.insert(eventToAdd->getEventName()); + } + } + + return AudioManager::addAudioEvent(eventToAdd); +} +#endif + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::audioDebugDisplay(DebugDisplayInterface* dd, void*, FILE* fp) +{ + std::list::iterator it; + + static char buffer[128] = { 0 }; + if (buffer[0] == 0) { + strncpy(buffer, alGetString(AL_VERSION), sizeof(buffer)); + } + + Coord3D lookPos; + TheTacticalView->getPosition(&lookPos); + lookPos.z = TheTerrainLogic->getGroundHeight(lookPos.x, lookPos.y); + const Coord3D *mikePos = TheAudio->getListenerPosition(); + Coord3D distanceVector = TheTacticalView->get3DCameraPosition(); + distanceVector.sub( mikePos ); + + Int now = TheGameLogic->getFrame(); + static Int lastCheck = now; + static Int latency = 0; + static Int worstLatency = 0; + + if( dd ) + { + dd->printf("OpenAL version: %s ", buffer); + dd->printf("Memory Usage : %d/%d\n", m_audioCache->getCurrentlyUsedSize(), m_audioCache->getMaxSize()); + dd->printf("Sound: %s ", (isOn(AudioAffect_Sound) ? "Yes" : "No")); + dd->printf("3DSound: %s ", (isOn(AudioAffect_Sound3D) ? "Yes" : "No")); + dd->printf("Speech: %s ", (isOn(AudioAffect_Speech) ? "Yes" : "No")); + dd->printf("Music: %s\n", (isOn(AudioAffect_Music) ? "Yes" : "No")); + dd->printf("Channels Available: "); + dd->printf("%d Sounds ", m_sound->getAvailableSamples()); + + dd->printf("%d 3D Sounds\n", m_sound->getAvailable3DSamples()); + dd->printf("Volume: "); + dd->printf("Sound: %d ", REAL_TO_INT(m_soundVolume * 100.0f)); + dd->printf("3DSound: %d ", REAL_TO_INT(m_sound3DVolume * 100.0f)); + dd->printf("Speech: %d ", REAL_TO_INT(m_speechVolume * 100.0f)); + dd->printf("Music: %d\n", REAL_TO_INT(m_musicVolume * 100.0f)); + dd->printf("Current 3D Provider: %s ", + + TheAudio->getProviderName(m_selectedProvider).str()); + dd->printf("Current Speaker Type: %s\n", TheAudio->translateUnsignedIntToSpeakerType(TheAudio->getSpeakerType()).str()); + + dd->printf( "Looking at: (%d,%d,%d) -- Microphone at: (%d,%d,%d)\n", + (Int)lookPos.x, (Int)lookPos.y, (Int)lookPos.z, (Int)mikePos->x, (Int)mikePos->y, (Int)mikePos->z ); + dd->printf( "Camera distance from microphone: %d -- Zoom Volume: %d%%\n", + (Int)distanceVector.length(), (Int)(TheAudio->getZoomVolume()*100.0f) ); + dd->printf( "Worst latency: %d -- Current latency: %d\n", worstLatency, latency ); + + dd->printf("-----------------------------------------------------------\n"); + dd->printf("Playing Audio\n"); + } + if( fp ) + { + fprintf( fp, "Miles Sound System version: %s ", buffer ); + fprintf( fp, "Memory Usage : %d/%d\n", m_audioCache->getCurrentlyUsedSize(), m_audioCache->getMaxSize() ); + fprintf( fp, "Sound: %s ", (isOn(AudioAffect_Sound) ? "Yes" : "No") ); + fprintf( fp, "3DSound: %s ", (isOn(AudioAffect_Sound3D) ? "Yes" : "No") ); + fprintf( fp, "Speech: %s ", (isOn(AudioAffect_Speech) ? "Yes" : "No") ); + fprintf( fp, "Music: %s\n", (isOn(AudioAffect_Music) ? "Yes" : "No") ); + fprintf( fp, "Channels Available: " ); + fprintf( fp, "%d Sounds ", m_sound->getAvailableSamples() ); + fprintf( fp, "%d 3D Sounds\n", m_sound->getAvailable3DSamples() ); + fprintf( fp, "Volume: "); + fprintf( fp, "Sound: %d ", REAL_TO_INT(m_soundVolume * 100.0f) ); + fprintf( fp, "3DSound: %d ", REAL_TO_INT(m_sound3DVolume * 100.0f) ); + fprintf( fp, "Speech: %d ", REAL_TO_INT(m_speechVolume * 100.0f) ); + fprintf( fp, "Music: %d\n", REAL_TO_INT(m_musicVolume * 100.0f) ); + fprintf( fp, "Current 3D Provider: %s ", TheAudio->getProviderName(m_selectedProvider).str()); + fprintf( fp, "Current Speaker Type: %s\n", TheAudio->translateUnsignedIntToSpeakerType(TheAudio->getSpeakerType()).str()); + + fprintf( fp, "Looking at: (%d,%d,%d) -- Microphone at: (%d,%d,%d)\n", + (Int)lookPos.x, (Int)lookPos.y, (Int)lookPos.z, (Int)mikePos->x, (Int)mikePos->y, (Int)mikePos->z ); + fprintf( fp, "Camera distance from microphone: %d -- Zoom Volume: %d%%\n", + (Int)distanceVector.length(), (Int)(TheAudio->getZoomVolume()*100.0f) ); + + fprintf( fp, "-----------------------------------------------------------\n" ); + fprintf( fp, "Playing Audio\n" ); + } + + PlayingAudio *playing = NULL; + Int channel; + Int channelCount; + Real volume = 0.0f; + AsciiString filenameNoSlashes; + + const Int maxChannels = 64; + PlayingAudio *playingArray[maxChannels] = { NULL }; + + // 2-D Sounds + if( dd ) + { + dd->printf("-----------------------------------------------------Sounds\n"); + channelCount = TheAudio->getNum2DSamples(); + channel = 1; + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (!playing) { + continue; + } + + playingArray[channel] = playing; + channel++; + } + + for (Int i = 1; i <= maxChannels && i <= channelCount; ++i) { + playing = playingArray[i]; + if (!playing) { + dd->printf("%d: Silence\n", i); + continue; + } + + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume(playing->m_audioEventRTS); + + dd->printf("%2d: %-20s - (%s) Volume: %d (2D)\n", i, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT(volume)); + playingArray[i] = NULL; + } + } + if( fp ) + { + fprintf( fp, "-----------------------------------------------------Sounds\n" ); + channelCount = TheAudio->getNum2DSamples(); + channel = 1; + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) + { + playing = *it; + if( !playing ) + { + continue; + } + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind( '\\' ) + 1; + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume( playing->m_audioEventRTS ); + + fprintf( fp, "%2d: %-20s - (%s) Volume: %d (2D)\n", channel++, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT( volume ) ); + } + for( int i = channel; i <= channelCount; ++i ) + { + fprintf( fp, "%d: Silence\n", i ); + } + } + + const Coord3D *microphonePos = TheAudio->getListenerPosition(); + + // Now 3D Sounds + if( dd ) + { + dd->printf("--------------------------------------------------3D Sounds\n"); + channelCount = TheAudio->getNum3DSamples(); + channel = 1; + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (!playing) { + continue; + } + + playingArray[channel] = playing; + channel++; + } + + for (Int i = 1; i <= maxChannels && i <= channelCount; ++i) + { + playing = playingArray[i]; + if (!playing) + { + dd->printf("%d: Silence\n", i); + continue; + } + + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume(playing->m_audioEventRTS); + Real dist = -1.0f; + const Coord3D *pos = playing->m_audioEventRTS->getPosition(); + char distStr[32]; + if( pos ) + { + Coord3D vector = *microphonePos; + vector.sub( pos ); + dist = vector.length(); + sprintf( distStr, "%d", REAL_TO_INT( dist ) ); + } + else + { + sprintf( distStr, "???" ); + } + char str[32]; + switch( playing->m_audioEventRTS->getOwnerType() ) + { + case OT_Positional: + sprintf( str, "(3D)" ); + break; + case OT_Object: + sprintf( str, "(3DObj)" ); + break; + case OT_Drawable: + sprintf( str, "(3DDraw)" ); + break; + case OT_Dead: + sprintf( str, "(3DDead)" ); + break; + + } + + dd->printf("%2d: %-20s - (%s) Volume: %d, Dist: %s, %s\n", + i, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT(volume), distStr, str ); + playingArray[i] = NULL; + } + } + if( fp ) + { + fprintf( fp, "--------------------------------------------------3D Sounds\n" ); + channelCount = TheAudio->getNum3DSamples(); + channel = 1; + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) + { + playing = *it; + if( !playing ) + { + continue; + } + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume( playing->m_audioEventRTS ); + fprintf( fp, "%2d: %-24s - (%s) Volume: %d \n", channel++, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT( volume ) ); + } + + for( int i = channel; i <= channelCount; ++i ) + { + fprintf( fp, "%2d: Silence\n", i ); + } + } + + // Now Streams + if( dd ) + { + dd->printf("----------------------------------------------------Streams\n"); + channelCount = TheAudio->getNumStreams(); + channel = 1; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (!playing) { + continue; + } + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; + + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume(playing->m_audioEventRTS); + + dd->printf("%2d: %-24s - (%s) Volume: %d (Stream)\n", channel++, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT(volume)); + } + + for ( int i = channel; i <= channelCount; ++i) { + dd->printf("%2d: Silence\n", i); + } + dd->printf("===========================================================\n"); + } + if( fp ) + { + fprintf( fp, "----------------------------------------------------Streams\n" ); + channelCount = TheAudio->getNumStreams(); + channel = 1; + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) + { + playing = *it; + if( !playing ) + { + continue; + } + filenameNoSlashes = playing->m_audioEventRTS->getFilename(); + filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; + + // Calculate Sample volume + volume = 100.0f; + volume *= getEffectiveVolume(playing->m_audioEventRTS); + + fprintf( fp, "%2d: %-24s - (%s) Volume: %d (Stream)\n", channel++, playing->m_audioEventRTS->getEventName().str(), filenameNoSlashes.str(), REAL_TO_INT( volume ) ); + } + + for( int i = channel; i <= channelCount; ++i ) + { + fprintf( fp, "%2d: Silence\n", i ); + } + fprintf( fp, "===========================================================\n" ); + } +} +#endif + +// Debug callback for OpenAL errors +static void AL_APIENTRY debugCallbackAL(ALenum source, ALenum type, ALuint id, + ALenum severity, ALsizei length, const ALchar* message, void* userParam ) AL_API_NOEXCEPT17 +{ + switch (severity) + { + case AL_DEBUG_SEVERITY_HIGH_EXT: + DEBUG_LOG(("OpenAL Error: %s", message)); + break; + case AL_DEBUG_SEVERITY_MEDIUM_EXT: + DEBUG_LOG(("OpenAL Warning: %s", message)); + break; + case AL_DEBUG_SEVERITY_LOW_EXT: + DEBUG_LOG(("OpenAL Info: %s", message)); + break; + default: + DEBUG_LOG(("OpenAL Message: %s", message)); + break; + } +} + +ALenum OpenALAudioManager::getALFormat(uint8_t channels, uint8_t bitsPerSample) +{ + if (channels == 1 && bitsPerSample == 8) + return AL_FORMAT_MONO8; + if (channels == 1 && bitsPerSample == 16) + return AL_FORMAT_MONO16; + if (channels == 1 && bitsPerSample == 32) + return AL_FORMAT_MONO_FLOAT32; + if (channels == 2 && bitsPerSample == 8) + return AL_FORMAT_STEREO8; + if (channels == 2 && bitsPerSample == 16) + return AL_FORMAT_STEREO16; + if (channels == 2 && bitsPerSample == 32) + return AL_FORMAT_STEREO_FLOAT32; + + DEBUG_LOG(("Unknown OpenAL format: %i channels, %i bits per sample", channels, bitsPerSample)); + return AL_FORMAT_MONO8; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::init() +{ + AudioManager::init(); +#ifdef INTENSE_DEBUG + DEBUG_LOG(("Sound has temporarily been disabled in debug builds only. jkmcd\n")); + // for now, RTS_DEBUG builds only should have no sound. ask jkmcd or srj about this. + return; +#endif + + // We should now know how many samples we want to load + openDevice(); + m_audioCache->setMaxSize(getAudioSettings()->m_maxCacheSize); + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::postProcessLoad() +{ + AudioManager::postProcessLoad(); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::reset() +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + dumpAllAssetsUsed(); + m_allEventsLoaded.clear(); +#endif + + AudioManager::reset(); + stopAllAudioImmediately(); + removeAllAudioRequests(); + // This must come after stopAllAudioImmediately() and removeAllAudioRequests(), to ensure that + // sounds pointing to the temporary AudioEventInfo handles are deleted before their info is deleted + removeLevelSpecificAudioEventInfos(); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::update() +{ + AudioManager::update(); + setDeviceListenerPosition(); + processRequestList(); + processPlayingList(); + processFadingList(); + processStoppedList(); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::stopAudio(AudioAffect which) +{ + // All we really need to do is: + // 1) Remove the EOS callback. + // 2) Stop the sample, (so that when we later unload it, bad stuff doesn't happen) + // 3) Set the status to stopped, so that when we next process the playing list, we will + // correctly clean up the sample. + + + std::list::iterator it; + + PlayingAudio *playing = NULL; + if (BitIsSet(which, AudioAffect_Sound)) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourceStop(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Sound3D)) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourceStop(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing) { + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; + } + } + alSourceStop(playing->m_source); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::pauseAudio(AudioAffect which) +{ + std::list::iterator it; + + PlayingAudio *playing = NULL; + if (BitIsSet(which, AudioAffect_Sound)) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourceStop(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Sound3D)) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourceStop(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing) { + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; + } + } + + alSourcePause(playing->m_source); + } + } + } + + //Get rid of PLAY audio requests when pausing audio. + std::list::iterator ait; + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); /* empty */) + { + AudioRequest *req = (*ait); + if( req && req->m_request == AR_Play ) + { + req->deleteInstance(); + ait = m_audioRequests.erase(ait); + } + else + { + ait++; + } + } +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::resumeAudio(AudioAffect which) +{ + std::list::iterator it; + + PlayingAudio *playing = NULL; + if (BitIsSet(which, AudioAffect_Sound)) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourcePlay(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Sound3D)) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing) { + alSourcePlay(playing->m_source); + } + } + } + + if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing) { + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; + } + } + //alSourcePlay(playing->m_stream->getSource()); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::pauseAmbient(Bool shouldPause) +{ + +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::playAudioEvent(AudioEventRTS* event) +{ +#ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG(("OPENAL (%d) - Processing play request: %d (%s)", TheGameLogic->getFrame(), event->getPlayingHandle(), event->getEventName().str())); +#endif + const AudioEventInfo *info = event->getAudioEventInfo(); + if (!info) { + return; + } + + std::list::iterator it; + PlayingAudio *playing = NULL; + + AudioHandle handleToKill = event->getHandleToKill(); + + AsciiString fileToPlay = event->getFilename(); + PlayingAudio *audio = allocatePlayingAudio(); + switch(info->m_soundType) + { + case AT_Music: + case AT_Streaming: + { + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG(("- Stream\n")); + #endif + + if ((info->m_soundType == AT_Streaming) && event->getUninterruptable()) { + stopAllSpeech(); + } + + Real curVolume = 1.0; + if (info->m_soundType == AT_Music) { + curVolume = m_musicVolume; + } else { + curVolume = m_speechVolume; + } + curVolume *= event->getVolume(); + + Bool foundSoundToReplace = false; + if (handleToKill) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = (*it); + if (!playing) { + continue; + } + + if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) + { + //Release this streaming channel immediately because we are going to play another sound in it's place. + releasePlayingAudio(playing); + m_playingStreams.erase(it); + foundSoundToReplace = true; + break; + } + } + } + + File* file = TheFileSystem->openFile(fileToPlay.str()); + if (!file) { + DEBUG_LOG(("Failed to open file: %s\n", fileToPlay.str())); + releasePlayingAudio(audio); + return; + } + + FFmpegFile* ffmpegFile = NEW FFmpegFile(); + if (!ffmpegFile->open(file)) + { + DEBUG_LOG(("Failed to open FFmpeg file: %s\n", fileToPlay.str())); + releasePlayingAudio(audio); + return; + } + + OpenALAudioStream* stream; + if (!handleToKill || foundSoundToReplace) { + //stream = new OpenALAudioStream; + //// When we need more data ask FFmpeg for more data. + //stream->setRequireDataCallback([ffmpegFile, stream]() { + // ffmpegFile->decodePacket(); + // }); + // + //// When we receive a frame from FFmpeg, send it to OpenAL. + //ffmpegFile->setFrameCallback([stream](AVFrame* frame, int stream_idx, int stream_type, void* user_data) { + // if (stream_type != AVMEDIA_TYPE_AUDIO) { + // return; + // } + + // DEBUG_LOG(("Received audio frame\n")); + + // AVSampleFormat sampleFmt = static_cast(frame->format); + // const int bytesPerSample = av_get_bytes_per_sample(sampleFmt); + // ALenum format = OpenALAudioManager::getALFormat(frame->ch_layout.nb_channels, bytesPerSample * 8); + // const int frameSize = + // av_samples_get_buffer_size(NULL, frame->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1); + // uint8_t* frameData = frame->data[0]; + + // // We need to interleave the samples if the format is planar + // if (av_sample_fmt_is_planar(static_cast(frame->format))) { + // uint8_t* audioBuffer = static_cast(av_malloc(frameSize)); + + // // Write the samples into our audio buffer + // for (int sample_idx = 0; sample_idx < frame->nb_samples; sample_idx++) + // { + // int byte_offset = sample_idx * bytesPerSample; + // for (int channel_idx = 0; channel_idx < frame->ch_layout.nb_channels; channel_idx++) + // { + // uint8_t* dst = &audioBuffer[byte_offset * frame->ch_layout.nb_channels + channel_idx * bytesPerSample]; + // uint8_t* src = &frame->data[channel_idx][byte_offset]; + // memcpy(dst, src, bytesPerSample); + // } + // } + // stream->bufferData(audioBuffer, frameSize, format, frame->sample_rate); + // av_freep(&audioBuffer); + // } + // else + // stream->bufferData(frameData, frameSize, format, frame->sample_rate); + // }); + + // Decode packets before starting the stream. + //for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) { + // if (!ffmpegFile->decodePacket()) + // break; + //} + } + else { + stream = NULL; + } + + // Put this on here, so that the audio event RTS will be cleaned up regardless. + audio->m_audioEventRTS = event; + audio->m_stream = stream; + audio->m_ffmpegFile = ffmpegFile; + audio->m_type = PAT_Stream; + + if (stream) { + if ((info->m_soundType == AT_Streaming) && event->getUninterruptable()) { + setDisallowSpeech(TRUE); + } + // AIL_set_stream_volume_pan(stream, curVolume, 0.5f); + playStream(event, stream); + m_playingStreams.push_back(audio); + audio = NULL; + } + break; + } + + case AT_SoundEffect: + { + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG(("- Sound")); + #endif + + + if (event->isPositionalAudio()) { + // Sounds that are non-global are positional 3-D sounds. Deal with them accordingly + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Positional")); + #endif + Bool foundSoundToReplace = false; + if (handleToKill) + { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = (*it); + if (!playing) { + continue; + } + + if( playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill ) + { + //Release this 3D sound channel immediately because we are going to play another sound in it's place. + releasePlayingAudio(playing); + m_playing3DSounds.erase(it); + foundSoundToReplace = true; + break; + } + } + } + + ALuint source; + if( !handleToKill || foundSoundToReplace ) + { + alGenSources(1, &source); + + } + else + { + source = 0; + } + // Push it onto the list of playing things + audio->m_audioEventRTS = event; + audio->m_source = source; + audio->m_bufferHandle = 0; + audio->m_type = PAT_3DSample; + m_playing3DSounds.push_back(audio); + + if (source) { + audio->m_bufferHandle = playSample3D(event, audio); + m_sound->notifyOf3DSampleStart(); + } + + if( !audio->m_bufferHandle ) + { + m_playing3DSounds.pop_back(); + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Killed (no handles available)\n")); + #endif + } + else + { + audio = NULL; + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Playing.\n")); + #endif + } + } + else + { + // UI sounds are always 2-D. All other sounds should be Positional + // Unit acknowledgement, etc, falls into the UI category of sound. + Bool foundSoundToReplace = false; + if (handleToKill) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = (*it); + if (!playing) { + continue; + } + + if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) + { + //Release this 2D sound channel immediately because we are going to play another sound in it's place. + releasePlayingAudio(playing); + m_playingSounds.erase(it); + foundSoundToReplace = true; + break; + } + } + } + + ALuint source; + if( !handleToKill || foundSoundToReplace ) + { + alGenSources(1, &source); + } + else + { + source = 0; + } + + // Push it onto the list of playing things + audio->m_audioEventRTS = event; + audio->m_source = source; + audio->m_bufferHandle = 0; + audio->m_type = PAT_Sample; + m_playingSounds.push_back(audio); + + if (source) { + audio->m_bufferHandle = playSample(event, audio); + m_sound->notifyOf2DSampleStart(); + } + + if (!audio->m_bufferHandle) { + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Killed (no handles available)\n")); + #endif + m_playingSounds.pop_back(); + } else { + audio = NULL; + } + + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Playing.\n")); + #endif + } + break; + } + } + + // If we were able to successfully play audio, then we set it to NULL above. (And it will be freed + // later. However, if audio is non-NULL at this point, then it must be freed. + if (audio) { + releasePlayingAudio(audio); + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::stopAudioEvent(AudioHandle handle) +{ +#ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG(("OPENAL (%d) - Processing stop request: %d\n", TheGameLogic->getFrame(), handle)); +#endif + + std::list::iterator it; + if (handle == AHSV_StopTheMusic || handle == AHSV_StopTheMusicFade) { + // for music, just find the currently playing music stream and kill it. + for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { + PlayingAudio *audio = (*it); + if (!audio) { + continue; + } + + if( audio->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music ) + { + if( handle == AHSV_StopTheMusicFade ) + { + m_fadingAudio.push_back(audio); + } + else + { + //m_stoppedAudio.push_back(audio); + releasePlayingAudio( audio ); + } + m_playingStreams.erase(it); + break; + } + } + } + + for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { + PlayingAudio *audio = (*it); + if (!audio) { + continue; + } + + if (audio->m_audioEventRTS->getPlayingHandle() == handle) { + // found it + audio->m_requestStop = true; + notifyOfAudioCompletion((UnsignedInt)(audio->m_source), PAT_Stream); + break; + } + } + + for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { + PlayingAudio *audio = (*it); + if (!audio) { + continue; + } + + if (audio->m_audioEventRTS->getPlayingHandle() == handle) { + audio->m_requestStop = true; + break; + } + } + + for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { + PlayingAudio *audio = (*it); + if (!audio) { + continue; + } + + if (audio->m_audioEventRTS->getPlayingHandle() == handle) { + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" (%s)\n", audio->m_audioEventRTS->getEventName())); + #endif + audio->m_requestStop = true; + break; + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::killAudioEventImmediately(AudioHandle audioEvent) +{ + //First look for it in the request list. + std::list::iterator ait; + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ait++) + { + AudioRequest *req = (*ait); + if( req && req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) + { + req->deleteInstance(); + ait = m_audioRequests.erase(ait); + return; + } + } + + //Look for matching 3D sound to kill + std::list::iterator it; + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); it++ ) + { + PlayingAudio *audio = (*it); + if( !audio ) + { + continue; + } + + if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) + { + releasePlayingAudio( audio ); + m_playing3DSounds.erase( it ); + return; + } + } + + //Look for matching 2D sound to kill + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); it++ ) + { + PlayingAudio *audio = (*it); + if( !audio ) + { + continue; + } + + if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) + { + releasePlayingAudio( audio ); + m_playingSounds.erase( it ); + return; + } + } + + //Look for matching steaming sound to kill + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); it++ ) + { + PlayingAudio *audio = (*it); + if( !audio ) + { + continue; + } + + if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) + { + releasePlayingAudio( audio ); + m_playingStreams.erase( it ); + return; + } + } + +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::pauseAudioEvent(AudioHandle handle) +{ + // pause audio +} + +//------------------------------------------------------------------------------------------------- +ALuint OpenALAudioManager::loadBufferForRead(AudioEventRTS* eventToLoadFrom) +{ + return m_audioCache->getBufferForFile(OpenFileInfo(eventToLoadFrom)); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::closeBuffer(ALuint bufferToClose) +{ + m_audioCache->closeBuffer(bufferToClose); +} + + +//------------------------------------------------------------------------------------------------- +PlayingAudio *OpenALAudioManager::allocatePlayingAudio(void) +{ + PlayingAudio *aud = NEW PlayingAudio; // poolify + return aud; +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::releaseOpenALHandles(PlayingAudio *release) +{ + if( release->m_source ) + { + alDeleteSources(1, &release->m_source); + release->m_source = 0; + } + if( release->m_stream ) + { + delete release->m_stream; + release->m_stream = NULL; + } + if( release->m_ffmpegFile ) + { + delete release->m_ffmpegFile; + release->m_ffmpegFile = NULL; + } + + release->m_type = PAT_INVALID; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::releasePlayingAudio(PlayingAudio *release) +{ + if (release->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_SoundEffect) { + if (release->m_type == PAT_Sample) { + if (release->m_source) { + m_sound->notifyOf2DSampleCompletion(); + } + } else { + if (release->m_source) { + m_sound->notifyOf3DSampleCompletion(); + } + } + } + releaseOpenALHandles(release); // forces stop of this audio + closeBuffer(release->m_bufferHandle); + if (release->m_cleanupAudioEventRTS) { + releaseAudioEventRTS(release->m_audioEventRTS); + } + delete release; + release = NULL; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::stopAllAudioImmediately(void) +{ + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { + playing = *it; + if (!playing) { + continue; + } + + releasePlayingAudio(playing); + it = m_playingSounds.erase(it); + } + + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { + playing = *it; + if (!playing) { + continue; + } + + releasePlayingAudio(playing); + it = m_playing3DSounds.erase(it); + } + + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + playing = (*it); + if (!playing) { + continue; + } + + releasePlayingAudio(playing); + it = m_playingStreams.erase(it); + } + + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ) { + playing = (*it); + if (!playing) { + continue; + } + + releasePlayingAudio(playing); + it = m_fadingAudio.erase(it); + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::freeAllOpenALHandles(void) +{ + // First, we need to ensure that we don't have any sample handles open. To that end, we must stop + // all of our currently playing audio. + stopAllAudioImmediately(); + + m_num2DSamples = 0; + m_num3DSamples = 0; + m_numStreams = 0; +} + +//------------------------------------------------------------------------------------------------- +//HSAMPLE OpenALAudioManager::getFirst2DSample(AudioEventRTS* event) +//{ +// if (m_availableSamples.begin() != m_availableSamples.end()) { +// HSAMPLE retSample = *m_availableSamples.begin(); +// m_availableSamples.erase(m_availableSamples.begin()); +// return (retSample); +// } +// +// // Find the first sample of lower priority than my augmented priority that is interruptable and take its handle +// +// return NULL; +//} +// +////------------------------------------------------------------------------------------------------- +//PlayingAudio *OpenALAudioManager::getFirst3DSample(AudioEventRTS* event) +//{ +// if (m_available3DSamples.begin() != m_available3DSamples.end()) { +// H3DSAMPLE retSample = *m_available3DSamples.begin(); +// m_available3DSamples.erase(m_available3DSamples.begin()); +// return (retSample); +// } +// +// // Find the first sample of lower priority than my augmented priority that is interruptable and take its handle +// return NULL; +//} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::adjustPlayingVolume(PlayingAudio *audio) +{ + Real desiredVolume = audio->m_audioEventRTS->getVolume() * audio->m_audioEventRTS->getVolumeShift(); + if (audio->m_type == PAT_Sample) { + alSourcef(audio->m_source, AL_GAIN, m_soundVolume * desiredVolume); + + } + else if (audio->m_type == PAT_3DSample) { + alSourcef(audio->m_source, AL_GAIN, m_sound3DVolume * desiredVolume); + + } + else if (audio->m_type == PAT_Stream) { + if (audio->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + //alSourcef(audio->m_stream->getSource(), AL_GAIN, m_musicVolume * desiredVolume); + } + else { + //alSourcef(audio->m_stream->getSource(), AL_GAIN, m_speechVolume * desiredVolume); + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::stopAllSpeech(void) +{ + std::list::iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + playing = (*it); + if (!playing) { + continue; + } + + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Streaming) { + releasePlayingAudio(playing); + it = m_playingStreams.erase(it); + } else { + ++it; + } + } + +} + +//------------------------------------------------------------------------------------------------- +//void OpenALAudioManager::initFilters(HSAMPLE sample, const AudioEventRTS* event) +//{ +// // set the sample volume +// Real volume = event->getVolume() * event->getVolumeShift() * m_soundVolume; +// AIL_set_sample_volume_pan(sample, volume, 0.5f); +// +// // pitch shift +// Real pitchShift = event->getPitchShift(); +// if (pitchShift == 0.0f) { +// DEBUG_CRASH(("Invalid Pitch shift in sound: '%s'", event->getEventName().str())); +// } +// else { +// AIL_set_sample_playback_rate(sample, REAL_TO_INT(AIL_sample_playback_rate(sample) * pitchShift)); +// } +// +// // set up delay filter, if applicable +// if (event->getDelay() > 0.0f) { +// Real value; +// value = event->getDelay(); +// AIL_set_sample_processor(sample, DP_FILTER, m_delayFilter); +// AIL_set_filter_sample_preference(sample, "Mono Delay Time", &value); +// +// value = 0.0; +// AIL_set_filter_sample_preference(sample, "Mono Delay", &value); +// AIL_set_filter_sample_preference(sample, "Mono Delay Mix", &value); +// } +//} + +//------------------------------------------------------------------------------------------------- +//void OpenALAudioManager::initFilters3D(H3DSAMPLE sample, const AudioEventRTS* event, const Coord3D* pos) +//{ +// // set the sample volume +// Real volume = event->getVolume() * event->getVolumeShift() * m_sound3DVolume; +// AIL_set_3D_sample_volume(sample, volume); +// +// // pitch shift +// Real pitchShift = event->getPitchShift(); +// if (pitchShift == 0.0f) { +// DEBUG_CRASH(("Invalid Pitch shift in sound: '%s'", event->getEventName().str())); +// } +// else { +// AIL_set_3D_sample_playback_rate(sample, REAL_TO_INT(AIL_3D_sample_playback_rate(sample) * pitchShift)); +// } +// +// // Low pass filter +// if (event->getAudioEventInfo()->m_lowPassFreq > 0 && !isOnScreen(pos)) { +// AIL_set_3D_sample_occlusion(sample, 1.0f - event->getAudioEventInfo()->m_lowPassFreq); +// } +//} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::nextMusicTrack(void) +{ + AsciiString trackName; + std::list::iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + trackName = playing->m_audioEventRTS->getEventName(); + } + } + + // Stop currently playing music + TheAudio->removeAudioEvent(AHSV_StopTheMusic); + + trackName = nextTrackName(trackName); + AudioEventRTS newTrack(trackName); + TheAudio->addAudioEvent(&newTrack); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::prevMusicTrack(void) +{ + AsciiString trackName; + std::list::iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + trackName = playing->m_audioEventRTS->getEventName(); + } + } + + // Stop currently playing music + TheAudio->removeAudioEvent(AHSV_StopTheMusic); + + trackName = prevTrackName(trackName); + AudioEventRTS newTrack(trackName); + TheAudio->addAudioEvent(&newTrack); +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isMusicPlaying( void ) const +{ + std::list::const_iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + return TRUE; + } + } + + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::hasMusicTrackCompleted(const AsciiString& trackName, Int numberOfTimes) const +{ + std::list::const_iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (playing->m_audioEventRTS->getEventName() == trackName) { + //if (INFINITE_LOOP_COUNT - AIL_stream_loop_count(playing->m_stream) >= numberOfTimes) { + // return TRUE; + //} + } + } + } + + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +AsciiString OpenALAudioManager::getMusicTrackName(void) const +{ + // First check the requests. If there's one there, then report that as the currently playing track. + std::list::const_iterator ait; + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { + if ((*ait)->m_request != AR_Play) { + continue; + } + + if (!(*ait)->m_usePendingEvent) { + continue; + } + + if ((*ait)->m_pendingEvent->getAudioEventInfo()->m_soundType == AT_Music) { + return (*ait)->m_pendingEvent->getEventName(); + } + } + + std::list::const_iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + return playing->m_audioEventRTS->getEventName(); + } + } + + return AsciiString::TheEmptyString; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::openDevice(void) +{ + if (!TheGlobalData->m_audioOn) { + return; + } + + // AIL_quick_startup should be replaced later with a call to actually pick which device to use, etc + const AudioSettings *audioSettings = getAudioSettings(); + m_selectedSpeakerType = TheAudio->translateSpeakerTypeToUnsignedInt(m_prefSpeaker); + + enumerateDevices(); + + m_alcDevice = alcOpenDevice(NULL); + if (m_alcDevice == nullptr) { + DEBUG_LOG(("Failed to open ALC device")); + // if we couldn't initialize any devices, turn sound off (fail silently) + setOn(false, AudioAffect_All); + return; + } + + ALCint attributes[] = { ALC_FREQUENCY, audioSettings->m_outputRate, 0 /* end-of-list */ }; + m_alcContext = alcCreateContext(m_alcDevice, attributes); + if (m_alcContext == nullptr) { + DEBUG_LOG(("Failed to create ALC context")); + setOn(false, AudioAffect_All); + return; + } + + if (!alcMakeContextCurrent(m_alcContext)) { + DEBUG_LOG(("Failed to make ALC context current")); + setOn(false, AudioAffect_All); + return; + } + + if (alcIsExtensionPresent(m_alcDevice, "ALC_EXT_debug")) { + auto alDebugMessageCallbackEXT = LPALDEBUGMESSAGECALLBACKEXT{}; + LOAD_ALC_PROC(alDebugMessageCallbackEXT); + alEnable(AL_DEBUG_OUTPUT_EXT); + alDebugMessageCallbackEXT(debugCallbackAL, nullptr); + } + + selectProvider(TheAudio->getProviderIndex(m_pref3DProvider)); + + // Now that we're all done, update the cached variables so that everything is in sync. + TheAudio->refreshCachedVariables(); + + if (!isValidProvider()) { + return; + } + + initDelayFilter(); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::closeDevice(void) +{ + unselectProvider(); + alcMakeContextCurrent(nullptr); + + if (m_alcContext) + alcDestroyContext(m_alcContext); + + if (m_alcDevice) + alcCloseDevice(m_alcDevice); +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isCurrentlyPlaying(AudioHandle handle) +{ + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + return true; + } + } + + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + return true; + } + } + + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + return true; + } + } + + // if something is requested, it is also considered playing + std::list::iterator ait; + AudioRequest *req = NULL; + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { + req = *ait; + if (req && req->m_usePendingEvent && req->m_pendingEvent->getPlayingHandle() == handle) { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::notifyOfAudioCompletion(UnsignedInt audioCompleted, UnsignedInt flags) +{ + PlayingAudio *playing = findPlayingAudioFrom(audioCompleted, flags); + if (!playing) { + DEBUG_CRASH(("Audio has completed playing, but we can't seem to find it. - jkmcd")); + return; + } + + if (getDisallowSpeech() && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Streaming) { + setDisallowSpeech(FALSE); + } + + if (playing->m_audioEventRTS->getAudioEventInfo()->m_control & AC_LOOP) { + if (playing->m_audioEventRTS->getNextPlayPortion() == PP_Attack) { + playing->m_audioEventRTS->setNextPlayPortion(PP_Sound); + } + if (playing->m_audioEventRTS->getNextPlayPortion() == PP_Sound) { + // First, decrease the loop count. + playing->m_audioEventRTS->decreaseLoopCount(); + + // Now, try to start the next loop + if (startNextLoop(playing)) { + return; + } + } + } + + playing->m_audioEventRTS->advanceNextPlayPortion(); + if (playing->m_audioEventRTS->getNextPlayPortion() != PP_Done) { + if (playing->m_type == PAT_Sample) { + closeBuffer(playing->m_bufferHandle); // close it so as not to leak it. + playing->m_bufferHandle = playSample(playing->m_audioEventRTS, playing); + + // If we don't have a file now, then we should drop to the stopped status so that + // We correctly close this handle. + if (playing->m_bufferHandle) { + return; + } + } else if (playing->m_type == PAT_3DSample) { + closeBuffer(playing->m_bufferHandle); // close it so as not to leak it. + playing->m_bufferHandle = playSample3D(playing->m_audioEventRTS, playing); + + // If we don't have a file now, then we should drop to the stopped status so that + // We correctly close this handle. + if (playing->m_bufferHandle) { + return; + } + } + } + + if (playing->m_type == PAT_Stream) { + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + playStream(playing->m_audioEventRTS, playing->m_stream); + + return; + } + } +} + +//------------------------------------------------------------------------------------------------- +PlayingAudio *OpenALAudioManager::findPlayingAudioFrom(ALuint source, UnsignedInt flags) +{ + std::list::iterator it; + PlayingAudio *playing; + + if (flags == PAT_Sample) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_source == source) { + return playing; + } + } + } + + if (flags == PAT_3DSample) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_source == source) { + return playing; + } + } + } + + if (flags == PAT_Stream) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_source == source) { + return playing; + } + } + } + + return NULL; +} + + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getProviderCount(void) const +{ + return m_providerCount; +} + +//------------------------------------------------------------------------------------------------- +AsciiString OpenALAudioManager::getProviderName(UnsignedInt providerNum) const +{ + if (isOn(AudioAffect_Sound3D) && providerNum < m_providerCount) { + return m_provider3D[providerNum].name; + } + + return AsciiString::TheEmptyString; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getProviderIndex(AsciiString providerName) const +{ + for (UnsignedInt i = 0; i < m_providerCount; ++i) { + if (providerName == m_provider3D[i].name) { + return i; + } + } + + return PROVIDER_ERROR; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::selectProvider(UnsignedInt providerNdx) +{ + if (!isOn(AudioAffect_Sound3D)) + { + return; + } + + if (providerNdx == m_selectedProvider) + { + return; + } + + if (isValidProvider()) + { + freeAllOpenALHandles(); + unselectProvider(); + } + + /*LPDIRECTSOUND lpDirectSoundInfo; + AIL_get_DirectSound_info(NULL, (void**)&lpDirectSoundInfo, NULL); + Bool useDolby = FALSE; + if (lpDirectSoundInfo) + { + DWORD speakerConfig; + lpDirectSoundInfo->GetSpeakerConfig(&speakerConfig); + switch (DSSPEAKER_CONFIG(speakerConfig)) + { + case DSSPEAKER_DIRECTOUT: + m_selectedSpeakerType = AIL_3D_2_SPEAKER; + break; + case DSSPEAKER_MONO: + m_selectedSpeakerType = AIL_3D_2_SPEAKER; + break; + case DSSPEAKER_STEREO: + m_selectedSpeakerType = AIL_3D_2_SPEAKER; + break; + case DSSPEAKER_HEADPHONE: + m_selectedSpeakerType = AIL_3D_HEADPHONE; + useDolby = TRUE; + break; + case DSSPEAKER_QUAD: + m_selectedSpeakerType = AIL_3D_4_SPEAKER; + useDolby = TRUE; + break; + case DSSPEAKER_SURROUND: + m_selectedSpeakerType = AIL_3D_SURROUND; + useDolby = TRUE; + break; + case DSSPEAKER_5POINT1: + m_selectedSpeakerType = AIL_3D_51_SPEAKER; + useDolby = TRUE; + break; + case DSSPEAKER_7POINT1: + m_selectedSpeakerType = AIL_3D_71_SPEAKER; + useDolby = TRUE; + break; + } + } + + if (useDolby) + { + providerNdx = getProviderIndex("Dolby Surround"); + } + else + { + providerNdx = getProviderIndex("Miles Fast 2D Positional Audio"); + } + success = AIL_open_3D_provider(m_provider3D[providerNdx].id) == 0;*/ + + //if (providerNdx < m_providerCount) + //{ + // failed = AIL_open_3D_provider(m_provider3D[providerNdx].id); + //} + + Bool success = FALSE; + + if( !success ) + { + m_selectedProvider = PROVIDER_ERROR; + // try to select a failsafe + providerNdx = getProviderIndex("Miles Fast 2D Positional Audio"); + success = TRUE; + } + + if ( success ) + { + m_selectedProvider = providerNdx; + + initSamplePools(); + + createListener(); + setSpeakerType(m_selectedSpeakerType); + if (TheVideoPlayer) + { + TheVideoPlayer->notifyVideoPlayerOfNewProvider(TRUE); + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::unselectProvider(void) +{ + if (!(isOn(AudioAffect_Sound3D) && isValidProvider())) { + return; + } + + if (TheVideoPlayer) { + TheVideoPlayer->notifyVideoPlayerOfNewProvider(FALSE); + } + m_lastProvider = m_selectedProvider; + + m_selectedProvider = PROVIDER_ERROR; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getSelectedProvider(void) const +{ + return m_selectedProvider; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::setSpeakerType(UnsignedInt speakerType) +{ + if (!isValidProvider()) { + return; + } + + //AIL_set_3D_speaker_type(m_provider3D[m_selectedProvider].id, speakerType); + m_selectedSpeakerType = speakerType; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getSpeakerType(void) +{ + if (!isValidProvider()) { + return 0; + } + + return m_selectedSpeakerType; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getNum2DSamples(void) const +{ + return m_num2DSamples; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getNum3DSamples(void) const +{ + return m_num3DSamples; +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt OpenALAudioManager::getNumStreams(void) const +{ + return m_numStreams; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::doesViolateLimit(AudioEventRTS* event) const +{ + Int limit = event->getAudioEventInfo()->m_limit; + if (limit == 0) { + return false; + } + + Int totalCount = 0; + Int totalRequestCount = 0; + + std::list::const_iterator it; + if (!event->isPositionalAudio()) { + // 2-D + for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getEventName() == event->getEventName()) { + if (totalCount == 0) { + // This is the oldest audio of this type playing. + event->setHandleToKill((*it)->m_audioEventRTS->getPlayingHandle()); + } + ++totalCount; + } + } + } else { + // 3-D + for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getEventName() == event->getEventName()) { + if (totalCount == 0) { + // This is the oldest audio of this type playing. + event->setHandleToKill((*it)->m_audioEventRTS->getPlayingHandle()); + } + ++totalCount; + } + } + } + + // Also check the request list in case we've requested to play this sound. + std::list::const_iterator arIt; + for (arIt = m_audioRequests.begin(); arIt != m_audioRequests.end(); ++arIt) { + AudioRequest *req = (*arIt); + if (req == NULL) { + continue; + } + if( req->m_usePendingEvent ) + { + if( req->m_pendingEvent->getEventName() == event->getEventName() ) + { + totalRequestCount++; + totalCount++; + } + } + } + + //If our event is an interrupting type, then normally we would always add it. The exception is when we have requested + //multiple sounds in the same frame and those requests violate the limit. Because we don't have any "old" sounds to + //remove in the case of an interrupt, we need to catch it early and prevent the sound from being added if we already + //reached the limit + if( event->getAudioEventInfo()->m_control & AC_INTERRUPT ) + { + if( totalRequestCount < limit ) + { + Int totalPlayingCount = totalCount - totalRequestCount; + if( totalRequestCount + totalPlayingCount < limit ) + { + //We aren't exceeding the actual limit, then clear the kill handle. + event->setHandleToKill(0); + return false; + } + + //We are exceeding the limit - the kill handle will kill the + //oldest playing sound to enforce the actual limit. + return false; + } + } + + if( totalCount < limit ) + { + event->setHandleToKill(0); + return false; + } + + return true; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isPlayingAlready(AudioEventRTS* event) const +{ + std::list::const_iterator it; + if (!event->isPositionalAudio()) { + // 2-D + for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getEventName() == event->getEventName()) { + return true; + } + } + } else { + // 3-D + for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getEventName() == event->getEventName()) { + return true; + } + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isObjectPlayingVoice(UnsignedInt objID) const +{ + if (objID == 0) { + return false; + } + + std::list::const_iterator it; + // 2-D + for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getObjectID() == objID && (*it)->m_audioEventRTS->getAudioEventInfo()->m_type & ST_VOICE) { + return true; + } + } + + // 3-D + for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getObjectID() == objID && (*it)->m_audioEventRTS->getAudioEventInfo()->m_type & ST_VOICE) { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +AudioEventRTS* OpenALAudioManager::findLowestPrioritySound(AudioEventRTS* event) +{ + AudioPriority priority = event->getAudioEventInfo()->m_priority; + if( priority == AP_LOWEST ) + { + //If the event we pass in is the lowest priority, don't bother checking because + //there is nothing lower priority than lowest. + return NULL; + } + AudioEventRTS *lowestPriorityEvent = NULL; + AudioPriority lowestPriority; + + std::list::const_iterator it; + if( event->isPositionalAudio() ) + { + //3D + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) + { + AudioEventRTS *itEvent = (*it)->m_audioEventRTS; + AudioPriority itPriority = itEvent->getAudioEventInfo()->m_priority; + if( itPriority < priority ) + { + if( !lowestPriorityEvent || lowestPriority > itPriority ) + { + lowestPriorityEvent = itEvent; + lowestPriority = itPriority; + if( lowestPriority == AP_LOWEST ) + { + return lowestPriorityEvent; + } + } + } + } + } + else + { + //2D + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) + { + AudioEventRTS *itEvent = (*it)->m_audioEventRTS; + AudioPriority itPriority = itEvent->getAudioEventInfo()->m_priority; + if( itPriority < priority ) + { + if( !lowestPriorityEvent || lowestPriority > itPriority ) + { + lowestPriorityEvent = itEvent; + lowestPriority = itPriority; + if( lowestPriority == AP_LOWEST ) + { + return lowestPriorityEvent; + } + } + } + } + } + return lowestPriorityEvent; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isPlayingLowerPriority(AudioEventRTS* event) const +{ + //We don't actually want to do anything to this CONST function. Remember, we're + //just checking to see if there is a lower priority sound. + AudioPriority priority = event->getAudioEventInfo()->m_priority; + if( priority == AP_LOWEST ) + { + //If the event we pass in is the lowest priority, don't bother checking because + //there is nothing lower priority than lowest. + return false; + } + std::list::const_iterator it; + if (!event->isPositionalAudio()) { + // 2-D + for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getAudioEventInfo()->m_priority < priority) { + //event->setHandleToKill((*it)->m_audioEventRTS->getPlayingHandle()); + return true; + } + } + } else { + // 3-D + for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { + if ((*it)->m_audioEventRTS->getAudioEventInfo()->m_priority < priority) { + //event->setHandleToKill((*it)->m_audioEventRTS->getPlayingHandle()); + return true; + } + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::killLowestPrioritySoundImmediately(AudioEventRTS* event) +{ + //Actually, we want to kill the LOWEST PRIORITY SOUND, not the first "lower" priority + //sound we find, because it could easily be + AudioEventRTS *lowestPriorityEvent = findLowestPrioritySound( event ); + if (lowestPriorityEvent) + { + std::list::iterator it; + if (event->isPositionalAudio()) + { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) + { + PlayingAudio *playing = (*it); + if (!playing) + { + continue; + } + + if (playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent) + { + //Release this 3D sound channel immediately because we are going to play another sound in it's place. + releasePlayingAudio(playing); + m_playing3DSounds.erase(it); + return TRUE; + } + } + } + else + { + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) + { + PlayingAudio *playing = (*it); + if( !playing ) + { + continue; + } + + if( playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent ) + { + //Release this sound channel immediately because we are going to play another sound in it's place. + releasePlayingAudio( playing ); + m_playingSounds.erase( it ); + return TRUE; + } + } + } + } + return FALSE; +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real newVolume) +{ + std::list::iterator it; + + PlayingAudio *playing = NULL; + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getEventName() == eventName) { + // Adjust it + playing->m_audioEventRTS->setVolume(newVolume); + Real desiredVolume = playing->m_audioEventRTS->getVolume() * playing->m_audioEventRTS->getVolumeShift(); + alSourcef(playing->m_source, AL_GAIN, desiredVolume); + } + } + + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getEventName() == eventName) { + // Adjust it + playing->m_audioEventRTS->setVolume(newVolume); + Real desiredVolume = playing->m_audioEventRTS->getVolume() * playing->m_audioEventRTS->getVolumeShift(); + alSourcef(playing->m_source, AL_GAIN, desiredVolume); + } + } + + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing && playing->m_audioEventRTS->getEventName() == eventName) { + // Adjust it + playing->m_audioEventRTS->setVolume(newVolume); + Real desiredVolume = playing->m_audioEventRTS->getVolume() * playing->m_audioEventRTS->getVolumeShift(); + alSourcef(playing->m_source, AL_GAIN, desiredVolume); + } + } +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::removePlayingAudio(AsciiString eventName) +{ + std::list::iterator it; + + PlayingAudio *playing = NULL; + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getEventName() == eventName ) + { + releasePlayingAudio( playing ); + it = m_playingSounds.erase(it); + } + else + { + it++; + } + } + + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getEventName() == eventName ) + { + releasePlayingAudio( playing ); + it = m_playing3DSounds.erase(it); + } + else + { + it++; + } + } + + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getEventName() == eventName ) + { + releasePlayingAudio( playing ); + it = m_playingStreams.erase(it); + } + else + { + it++; + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::removeAllDisabledAudio() +{ + std::list::iterator it; + + PlayingAudio *playing = NULL; + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + { + releasePlayingAudio( playing ); + it = m_playingSounds.erase(it); + } + else + { + it++; + } + } + + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + { + releasePlayingAudio( playing ); + it = m_playing3DSounds.erase(it); + } + else + { + it++; + } + } + + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + { + playing = *it; + if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + { + releasePlayingAudio( playing ); + it = m_playingStreams.erase(it); + } + else + { + it++; + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::processRequestList(void) +{ + std::list::iterator it; + for (it = m_audioRequests.begin(); it != m_audioRequests.end(); /* empty */) { + AudioRequest *req = (*it); + if (req == NULL) { + continue; + } + + if (!shouldProcessRequestThisFrame(req)) { + adjustRequest(req); + ++it; + continue; + } + + if (!req->m_requiresCheckForSample || checkForSample(req)) { + processRequest(req); + } + req->deleteInstance(); + it = m_audioRequests.erase(it); + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::processPlayingList(void) +{ + // There are two types of processing we have to do here. + // 1. Move the item to the stopped list if it has become stopped. + // 2. Update the position of the audio if it is positional + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); /* empty */) { + playing = (*it); + if (!playing) + { + it = m_playingSounds.erase(it); + continue; + } + + if (sourceIsStopped(playing->m_source)) + { + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_playingSounds.erase(it); + } + else + { + if (m_volumeHasChanged) + { + adjustPlayingVolume(playing); + } + ++it; + } + } + + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + { + playing = (*it); + if (!playing) + { + it = m_playing3DSounds.erase(it); + continue; + } + + if (sourceIsStopped(playing->m_source)) + { + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_playing3DSounds.erase(it); + } + else + { + if (m_volumeHasChanged) + { + adjustPlayingVolume(playing); + } + + const Coord3D *pos = getCurrentPositionFromEvent(playing->m_audioEventRTS); + if (pos) + { + if( playing->m_audioEventRTS->isDead() ) + { + stopAudioEvent( playing->m_audioEventRTS->getPlayingHandle() ); + it++; + continue; + } + else + { + Real volForConsideration = getEffectiveVolume(playing->m_audioEventRTS); + volForConsideration /= (m_sound3DVolume > 0.0f ? m_soundVolume : 1.0f); + Bool playAnyways = BitIsSet(playing->m_audioEventRTS->getAudioEventInfo()->m_type, ST_GLOBAL) || playing->m_audioEventRTS->getAudioEventInfo()->m_priority == AP_CRITICAL; + if( volForConsideration < m_audioSettings->m_minVolume && !playAnyways ) + { + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_playing3DSounds.erase(it); + continue; + } + else + { + Real x = pos->x; + Real y = pos->y; + Real z = pos->z; + alSource3f(playing->m_source, AL_POSITION, x, y, z); + DEBUG_LOG(("Updating 3D sound position for %s to %f, %f, %f\n", playing->m_audioEventRTS->getEventName().str(), x, y, z)); + } + } + } + else + { + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_playing3DSounds.erase(it); + continue; + } + + ++it; + } + } + + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + playing = (*it); + if (!playing) + { + it = m_playingStreams.erase(it); + continue; + } + + if (sourceIsStopped(playing->m_source)) + { + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_playingStreams.erase(it); + } + else + { + if (m_volumeHasChanged) + { + adjustPlayingVolume(playing); + } + + //playing->m_stream->update(); + ++it; + } + } + + if (m_volumeHasChanged) { + m_volumeHasChanged = false; + } +} + +//Patch for a rare bug (only on about 5% of in-studio machines suffer, and not all the time) . +//The actual mechanics of this problem are still elusive as of the date of this comment. 8/21/03 +//but the cause is clear. Some cinematics do a radical change in the microphone position, which +//calls for a radical 3DSoundVolume adjustment. If this happens while a stereo stream is *ENDING*, +//low-level code gets caught in a tight loop. (Hangs) on some machines. +//To prevent this condition, we just suppress the updating of 3DSoundVolume while one of these +//is on the list. Since the music tracks play continuously, they never *END* during these cinematics. +//so we filter them out as, *NOT SENSITIVE*... we do want to update 3DSoundVolume during music, +//which is almost all of the time. + +Bool OpenALAudioManager::has3DSensitiveStreamsPlaying(void) const +{ + if ( m_playingStreams.empty() ) + return FALSE; + + for ( std::list< PlayingAudio* >::const_iterator it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) + { + const PlayingAudio *playing = (*it); + + if ( !playing ) + continue; + + if ( playing->m_audioEventRTS->getAudioEventInfo()->m_soundType != AT_Music ) + { + return TRUE; + } + + if (playing->m_audioEventRTS->getEventName().startsWith("Game_") == FALSE ) + { + return TRUE; + } + } + + return FALSE; + +} + + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::processFadingList(void) +{ + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); /* emtpy */) { + playing = *it; + if (!playing) { + continue; + } + + if (playing->m_framesFaded >= getAudioSettings()->m_fadeAudioFrames) { + playing->m_requestStop = true; + //m_stoppedAudio.push_back(playing); + releasePlayingAudio( playing ); + it = m_fadingAudio.erase(it); + continue; + } + + ++playing->m_framesFaded; + Real volume = getEffectiveVolume(playing->m_audioEventRTS); + volume *= (1.0f - 1.0f * playing->m_framesFaded / getAudioSettings()->m_fadeAudioFrames); + + switch (playing->m_type) + { + case PAT_Sample: + { + alSourcef(playing->m_source, AL_GAIN, volume); + break; + } + + case PAT_3DSample: + { + alSourcef(playing->m_source, AL_GAIN, volume); + break; + } + + case PAT_Stream: + { + //alSourcef(playing->m_stream->getSource(), AL_GAIN, volume); + break; + } + + } + + ++it; + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::processStoppedList(void) +{ + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_stoppedAudio.begin(); it != m_stoppedAudio.end(); /* emtpy */) { + playing = *it; + if (playing) { + releasePlayingAudio(playing); + } + it = m_stoppedAudio.erase(it); + } +} + + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::shouldProcessRequestThisFrame(AudioRequest* req) const +{ + if (!req->m_usePendingEvent) { + return true; + } + + if (req->m_pendingEvent->getDelay() < MSEC_PER_LOGICFRAME_REAL) { + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::adjustRequest(AudioRequest* req) +{ + if (!req->m_usePendingEvent) { + return; + } + + req->m_pendingEvent->decrementDelay(MSEC_PER_LOGICFRAME_REAL); + req->m_requiresCheckForSample = true; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::checkForSample(AudioRequest* req) +{ + if (!req->m_usePendingEvent) { + return true; + } + + if ( req->m_pendingEvent->getAudioEventInfo() == NULL ) + { + // Fill in event info + getInfoForAudioEvent(req->m_pendingEvent); + } + + if (req->m_pendingEvent->getAudioEventInfo()->m_type != AT_SoundEffect) + { + return true; + } + + return m_sound->canPlayNow(req->m_pendingEvent); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::setHardwareAccelerated(Bool accel) +{ + // Extends + Bool retEarly = (accel == m_hardwareAccel); + AudioManager::setHardwareAccelerated(accel); + + if (retEarly) { + return; + } + + if (m_hardwareAccel) { + for (Int i = 0; i < MAX_HW_PROVIDERS; ++i) { + UnsignedInt providerNdx = TheAudio->getProviderIndex(TheAudio->getAudioSettings()->m_preferred3DProvider[i]); + TheAudio->selectProvider(providerNdx); + if (getSelectedProvider() == providerNdx) { + return; + } + } + } + + // set it false + AudioManager::setHardwareAccelerated(FALSE); + UnsignedInt providerNdx = TheAudio->getProviderIndex(TheAudio->getAudioSettings()->m_preferred3DProvider[MAX_HW_PROVIDERS]); + TheAudio->selectProvider(providerNdx); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::setSpeakerSurround(Bool surround) +{ + // Extends + Bool retEarly = (surround == m_surroundSpeakers); + AudioManager::setSpeakerSurround(surround); + + if (retEarly) { + return; + } + + UnsignedInt speakerType; + if (m_surroundSpeakers) { + speakerType = TheAudio->getAudioSettings()->m_defaultSpeakerType3D; + } else { + speakerType = TheAudio->getAudioSettings()->m_defaultSpeakerType2D; + } + + TheAudio->setSpeakerType(speakerType); +} + +//------------------------------------------------------------------------------------------------- +Real OpenALAudioManager::getFileLengthMS(AsciiString strToLoad) const +{ + if (strToLoad.isEmpty()) { + return 0.0f; + } + float length = 0.0f; + +#ifdef RTS_HAS_FFMPEG + ALuint handle = m_audioCache->getBufferForFile(OpenFileInfo(&strToLoad)); + length = m_audioCache->getBufferLength(handle); + m_audioCache->closeBuffer(handle); +#endif + + return length; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::closeAnySamplesUsingFile(const void* fileToClose) +{ + ALuint bufferHandle = (ALuint)(uintptr_t)fileToClose; + if (!bufferHandle) { + return; + } + + std::list::iterator it; + PlayingAudio *playing; + + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { + playing = *it; + if (!playing) { + continue; + } + + if (playing->m_bufferHandle == bufferHandle) { + releasePlayingAudio(playing); + it = m_playingSounds.erase(it); + } else { + ++it; + } + } + + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { + playing = *it; + if (!playing) { + continue; + } + + if (playing->m_bufferHandle == bufferHandle) { + releasePlayingAudio(playing); + it = m_playing3DSounds.erase(it); + } else { + ++it; + } + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::setDeviceListenerPosition(void) +{ + ALfloat listenerOri[] = { m_listenerOrientation.x, m_listenerOrientation.y, m_listenerOrientation.z, 0.0f, 0.0f, 1.0f }; + alListener3f(AL_POSITION, m_listenerPosition.x, m_listenerPosition.y, m_listenerPosition.z); + alListenerfv(AL_ORIENTATION, listenerOri); + DEBUG_LOG(("Listener Position: %f, %f, %f", m_listenerPosition.x, m_listenerPosition.y, m_listenerPosition.z)); +} + +//------------------------------------------------------------------------------------------------- +const Coord3D* OpenALAudioManager::getCurrentPositionFromEvent(AudioEventRTS* event) +{ + if (!event->isPositionalAudio()) { + return NULL; + } + + return event->getCurrentPosition(); +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isOnScreen(const Coord3D* pos) const +{ + static ICoord2D dummy; + // WorldToScreen will return True if the point is onscreen and false if it is offscreen. + return TheTacticalView->worldToScreen(pos, &dummy); +} + +//------------------------------------------------------------------------------------------------- +Real OpenALAudioManager::getEffectiveVolume(AudioEventRTS* event) const +{ + Real volume = 1.0f; + volume *= (event->getVolume() * event->getVolumeShift()); + if (event->getAudioEventInfo()->m_soundType == AT_Music) + { + volume *= m_musicVolume; + } + else if (event->getAudioEventInfo()->m_soundType == AT_Streaming) + { + volume *= m_speechVolume; + } + else + { + if (event->isPositionalAudio()) + { + volume *= m_sound3DVolume; + } + else + { + volume *= m_soundVolume; + } + } + + return volume; +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::startNextLoop(PlayingAudio *looping) +{ + closeBuffer(looping->m_bufferHandle); + looping->m_bufferHandle = 0; + + if (looping->m_requestStop) { + return false; + } + + if (looping->m_audioEventRTS->hasMoreLoops()) { + // generate a new filename, and test to see whether we can play with it now + looping->m_audioEventRTS->generateFilename(); + + if (looping->m_audioEventRTS->getDelay() > MSEC_PER_LOGICFRAME_REAL) { + // fake it out so that this sound appears done, but also so that it will not + // delete the sound on completion (which would suck) + looping->m_cleanupAudioEventRTS = false; + looping->m_requestStop = true; + + AudioRequest *req = allocateAudioRequest(true); + req->m_pendingEvent = looping->m_audioEventRTS; + req->m_requiresCheckForSample = true; + appendAudioRequest(req); + return true; + } + + if (looping->m_type == PAT_3DSample) { + looping->m_bufferHandle = playSample3D(looping->m_audioEventRTS, looping); + } else { + looping->m_bufferHandle = playSample(looping->m_audioEventRTS, looping); + } + + return looping->m_bufferHandle != 0; + } + return false; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::playStream(AudioEventRTS* event, OpenALAudioStream* stream) +{ + // Force it to the beginning + if (event->getAudioEventInfo()->m_soundType == AT_Music) { + //alSourcei(stream->getSource(), AL_LOOPING, AL_TRUE); + } + + //stream->play(); + if (event->getAudioEventInfo()->m_soundType == AT_Music) { + // Need to stop/fade out the old music here. + } +} + +//------------------------------------------------------------------------------------------------- +ALuint OpenALAudioManager::playSample(AudioEventRTS* event, PlayingAudio *audio) +{ + // Load the file in + ALuint bufferHandle = loadBufferForRead(event); + if (bufferHandle) { + alSourcei(audio->m_source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(audio->m_source, AL_BUFFER, (ALuint)(uintptr_t)bufferHandle); + alSourcePlay(audio->m_source); + } + + return bufferHandle; +} + +//------------------------------------------------------------------------------------------------- +ALuint OpenALAudioManager::playSample3D(AudioEventRTS* event, PlayingAudio *sample3D) +{ + const Coord3D *pos = getCurrentPositionFromEvent(event); + if (pos) { + ALuint handle = loadBufferForRead(event); + const AudioSettings* audioSettings = getAudioSettings(); + + if (handle) { + auto source = sample3D->m_source; + // Set the position values of the sample here + if (event->getAudioEventInfo()->m_type & ST_GLOBAL) { + alSourcef(source, AL_REFERENCE_DISTANCE, audioSettings->m_globalMinRange); + alSourcef(source, AL_MAX_DISTANCE, audioSettings->m_globalMaxRange); + } else { + alSourcef(source, AL_REFERENCE_DISTANCE, event->getAudioEventInfo()->m_minDistance); + alSourcef(source, AL_MAX_DISTANCE, event->getAudioEventInfo()->m_maxDistance); + } + + Real pitch = event->getPitchShift() != 0.0f ? event->getPitchShift() : 1.0f; + alSourcef(source, AL_PITCH, pitch); + alSourcef(source, AL_ROLLOFF_FACTOR, 0.5f); + + // Set the position of the sample here + Real x = pos->x; + Real y = pos->y; + Real z = pos->z; + alSource3f(source, AL_POSITION, x, y, z); + alSourcei(source, AL_BUFFER, handle); + DEBUG_LOG(("Playing 3D sample '%s' at %f, %f, %f\n", event->getEventName().str(), x, y, z)); + + // Start playback + alSourcePlay(source); + } + return handle; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::enumerateDevices(void) +{ + const ALCchar* devices = NULL; + if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") == AL_TRUE) { + devices = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + if ((devices == nullptr || *devices == '\0') && alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_TRUE) { + devices = alcGetString(NULL, ALC_DEVICE_SPECIFIER); + } + } + + if (devices == nullptr) { + DEBUG_LOG(("Enumerating OpenAL devices is not supported")); + return; + } + + const ALCchar* device = devices; + const ALCchar* next = devices + 1; + size_t len = 0; + size_t idx = 0; + while (device && *device != '\0' && next && *next != '\0' && idx < AL_MAX_PLAYBACK_DEVICES) { + m_alDevicesList[idx++] = device; + len = strlen(device); + device += (len + 1); + next += (len + 2); + } + + m_alMaxDevicesIndex = idx; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::createListener(void) +{ + // OpenAL only has one listener +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::initDelayFilter(void) +{ +} + +//------------------------------------------------------------------------------------------------- +Bool OpenALAudioManager::isValidProvider(void) +{ + return (m_selectedProvider < m_providerCount); +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::initSamplePools(void) +{ + if (!(isOn(AudioAffect_Sound3D) && isValidProvider())) + { + return; + } + + m_num2DSamples = getAudioSettings()->m_sampleCount2D; + m_num3DSamples = getAudioSettings()->m_sampleCount3D; + + // Streams are basically free, so we can just allocate the appropriate number + m_numStreams = getAudioSettings()->m_streamCount; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::processRequest(AudioRequest* req) +{ + switch (req->m_request) + { + case AR_Play: + { + playAudioEvent(req->m_pendingEvent); + break; + } + case AR_Pause: + { + pauseAudioEvent(req->m_handleToInteractOn); + break; + } + case AR_Stop: + { + stopAudioEvent(req->m_handleToInteractOn); + break; + } + } +} + +//------------------------------------------------------------------------------------------------- +void* OpenALAudioManager::getHandleForBink(void) +{ + if (!m_binkAudio) { + DEBUG_LOG(("Creating Bink audio stream\n")); + //m_binkAudio = NEW OpenALAudioStream; + } + return m_binkAudio; +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::releaseHandleForBink(void) +{ + if (m_binkAudio) { + DEBUG_LOG(("Releasing Bink audio stream\n")); + delete m_binkAudio; + m_binkAudio = NULL; + } +} + +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::friend_forcePlayAudioEventRTS(const AudioEventRTS* eventToPlay) +{ + if (!eventToPlay->getAudioEventInfo()) { + getInfoForAudioEvent(eventToPlay); + if (!eventToPlay->getAudioEventInfo()) { + DEBUG_CRASH(("No info for forced audio event '%s'\n", eventToPlay->getEventName().str())); + return; + } + } + + switch (eventToPlay->getAudioEventInfo()->m_soundType) + { + case AT_Music: + if (!isOn(AudioAffect_Music)) + return; + break; + case AT_SoundEffect: + if (!isOn(AudioAffect_Sound) || !isOn(AudioAffect_Sound3D)) + return; + break; + case AT_Streaming: + if (!isOn(AudioAffect_Speech)) + return; + break; + } + + AudioEventRTS event = *eventToPlay; + + event.generateFilename(); + event.generatePlayInfo(); + + std::list >::iterator it; + for (it = m_adjustedVolumes.begin(); it != m_adjustedVolumes.end(); ++it) { + if (it->first == event.getEventName()) { + event.setVolume(it->second); + break; + } + } + + playAudioEvent(&event); +} + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) +//------------------------------------------------------------------------------------------------- +void OpenALAudioManager::dumpAllAssetsUsed() +{ + if (!TheGlobalData->m_preloadReport) { + return; + } + + // Dump all the audio assets we've used. + FILE* logfile = fopen("PreloadedAssets.txt", "a+"); //append to log + if (!logfile) + return; + + std::list missingEvents; + std::list usedFiles; + + std::list::iterator lit; + + fprintf(logfile, "\nAudio Asset Report - BEGIN\n"); + { + SetAsciiStringIt it; + std::vector::iterator asIt; + for (it = m_allEventsLoaded.begin(); it != m_allEventsLoaded.end(); ++it) { + AsciiString astr = *it; + AudioEventInfo *aei = findAudioEventInfo(astr); + if (!aei) { + missingEvents.push_back(astr); + continue; + } + + for (asIt = aei->m_attackSounds.begin(); asIt != aei->m_attackSounds.end(); ++asIt) { + usedFiles.push_back(*asIt); + } + + for (asIt = aei->m_sounds.begin(); asIt != aei->m_sounds.end(); ++asIt) { + usedFiles.push_back(*asIt); + } + + for (asIt = aei->m_decaySounds.begin(); asIt != aei->m_decaySounds.end(); ++asIt) { + usedFiles.push_back(*asIt); + } + + if (!aei->m_filename.isEmpty()) { + usedFiles.push_back(aei->m_filename); + } + } + + fprintf(logfile, "\nEvents Requested that are missing information - BEGIN\n"); + for (lit = missingEvents.begin(); lit != missingEvents.end(); ++lit) { + fprintf(logfile, "%s\n", (*lit).str()); + } + fprintf(logfile, "\nEvents Requested that are missing information - END\n"); + + fprintf(logfile, "\nFiles Used - BEGIN\n"); + for (lit = usedFiles.begin(); lit != usedFiles.end(); ++lit) { + fprintf(logfile, "%s\n", (*lit).str()); + } + fprintf(logfile, "\nFiles Used - END\n"); + } + fprintf(logfile, "\nAudio Asset Report - END\n"); + fclose(logfile); + logfile = NULL; +} +#endif \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp index 345ce39d73..69b08bf064 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp @@ -47,8 +47,8 @@ extern "C" { } #ifdef RTS_HAS_OPENAL -#include "OpenALAudioDevice/OpenALAudioManager.h" -#include "OpenALAudioDevice/OpenALAudioStream.h" +#include "OpenALDevice/OpenALAudioManager.h" +//#include "OpenALDevice/OpenALAudioStream.h" #endif #include @@ -311,7 +311,7 @@ FFmpegVideoStream::FFmpegVideoStream(FFmpegFile* file) m_ffmpegFile->setFrameCallback(onFrame); m_ffmpegFile->setUserData(this); -#ifdef RTS_USE_OPENAL +#if 0//def RTS_HAS_OPENAL // Release the audio handle if it's already in use OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->reset(); @@ -321,7 +321,7 @@ FFmpegVideoStream::FFmpegVideoStream(FFmpegFile* file) while (m_good && m_gotFrame == false) m_good = m_ffmpegFile->decodePacket(); - #ifdef RTS_USE_OPENAL + #if 0//def RTS_HAS_OPENAL // Start audio playback audioStream->play(); #endif @@ -349,7 +349,7 @@ void FFmpegVideoStream::onFrame(AVFrame *frame, int stream_idx, int stream_type, videoStream->m_frame = av_frame_clone(frame); videoStream->m_gotFrame = true; } -#ifdef RTS_USE_OPENAL +#if 0//def RTS_USE_OPENAL else if (stream_type == AVMEDIA_TYPE_AUDIO) { OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->update(); @@ -394,7 +394,7 @@ void FFmpegVideoStream::onFrame(AVFrame *frame, int stream_idx, int stream_type, void FFmpegVideoStream::update( void ) { -#ifdef RTS_USE_OPENAL +#if 0//def RTS_HAS_OPENAL // Start audio playback OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->play(); From 50ba331cce32d2a00efd01fea1fce3554a34035e Mon Sep 17 00:00:00 2001 From: Stephan Vedder Date: Tue, 20 May 2025 16:54:32 +0200 Subject: [PATCH 4/4] [LINUX][ZH] Add OpenAL stream implementations --- .../Code/GameEngineDevice/CMakeLists.txt | 6 +- .../OpenALDevice/FFmpegOpenALAudioStream.h | 37 ++++++ .../Include/OpenALDevice/OpenALAudioStream.h | 56 +++++++++ .../Win32Device/Common/Win32GameEngine.h | 3 +- .../OpenALDevice/FFmpegOpenALAudioStream.cpp | 32 +++++ .../OpenALDevice/OpenALAudioManager.cpp | 103 ++++++++------- .../Source/OpenALDevice/OpenALAudioStream.cpp | 117 ++++++++++++++++++ .../VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp | 10 +- GeneralsMD/Code/Main/WinMain.cpp | 2 +- 9 files changed, 305 insertions(+), 61 deletions(-) create mode 100644 GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/FFmpegOpenALAudioStream.h create mode 100644 GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioStream.h create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/FFmpegOpenALAudioStream.cpp create mode 100644 GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioStream.cpp diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 921a61e8cb..aaf6c69573 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -254,12 +254,14 @@ if(RTS_BUILD_OPTION_OPENAL) if(OpenAL_FOUND) target_sources(z_gameenginedevice PRIVATE + Include/OpenALDevice/FFmpegOpenALAudioStream.h Include/OpenALDevice/OpenALAudioManager.h - # Include/OpenALDevice/OpenALAudioStream.h + Include/OpenALDevice/OpenALAudioStream.h + Source/OpenALDevice/FFmpegOpenALAudioStream.cpp Source/OpenALDevice/OpenALAudioFileCache.h Source/OpenALDevice/OpenALAudioFileCache.cpp Source/OpenALDevice/OpenALAudioManager.cpp - # Source/OpenALDevice/OpenALAudioStream.cpp + Source/OpenALDevice/OpenALAudioStream.cpp ) target_link_libraries(z_gameenginedevice PRIVATE OpenAL::OpenAL) diff --git a/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/FFmpegOpenALAudioStream.h b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/FFmpegOpenALAudioStream.h new file mode 100644 index 0000000000..bccb7d48f7 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/FFmpegOpenALAudioStream.h @@ -0,0 +1,37 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: FFmpegOpenALAudioStream.h ////////////////////////////////////////////////////////////////////////// +// FFmpegOpenALAudioStream implementation +// Author: Stephan Vedder, May 2025 +#pragma once + +#include "OpenALAudioManager.h" +#include "OpenALAudioStream.h" + +class FFmpegFile; +class FFmpegOpenALAudioStream : public OpenALAudioStream +{ +public: + FFmpegOpenALAudioStream(FFmpegFile* file); + ~FFmpegOpenALAudioStream(); + + +protected: + FFmpegFile* m_ffmpegFile; +}; \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioStream.h b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioStream.h new file mode 100644 index 0000000000..f7e979381c --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Include/OpenALDevice/OpenALAudioStream.h @@ -0,0 +1,56 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: OpenALAudioStream.h ////////////////////////////////////////////////////////////////////////// +// OpenALAudioStream implementation +// Author: Stephan Vedder, May 2025 +#pragma once + +#include "always.h" +#include +#include +#include + +#define AL_STREAM_BUFFER_COUNT 32 + +class OpenALAudioStream +{ +public: + OpenALAudioStream(); + virtual ~OpenALAudioStream(); + + void setRequireDataCallback(std::function callback) { m_requireDataCallback = callback; } + ALuint getSource() const { return m_source; } + + bool bufferData(uint8_t *data, size_t data_size, ALenum format, int samplerate); + bool isPlaying(); + void update(); + void reset(); + + void play() { alSourcePlay(m_source); } + void pause() { alSourcePause(m_source); } + void stop() { alSourceStop(m_source); } + + void setVolume(float vol) { alSourcef(m_source, AL_GAIN, vol); } + +protected: + std::function m_requireDataCallback = nullptr; + ALuint m_source = 0; + ALuint m_buffers[AL_STREAM_BUFFER_COUNT] = {}; + unsigned int m_currentBufferIndex = 0; +}; \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h index 8fb8aba29e..359c51db81 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h @@ -38,9 +38,10 @@ #include "Common/GameEngine.h" #include "GameLogic/GameLogic.h" #include "GameNetwork/NetworkInterface.h" -#include "MilesAudioDevice/MilesAudioManager.h" #ifdef RTS_HAS_OPENAL #include "OpenALDevice/OpenALAudioManager.h" +#else +#include "MilesAudioDevice/MilesAudioManager.h" #endif #include "Win32Device/Common/Win32BIGFileSystem.h" #include "Win32Device/Common/Win32LocalFileSystem.h" diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/FFmpegOpenALAudioStream.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/FFmpegOpenALAudioStream.cpp new file mode 100644 index 0000000000..f999f07062 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/FFmpegOpenALAudioStream.cpp @@ -0,0 +1,32 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// FILE: FFmpegOpenALAudioStream.cpp ////////////////////////////////////////////////////////////////////////// +// FFmpegOpenALAudioStream implementation +// Author: Stephan Vedder, May 2025 +#include "OpenALDevice/FFmpegOpenALAudioStream.h" + +FFmpegOpenALAudioStream::FFmpegOpenALAudioStream(FFmpegFile* file) : + OpenALAudioStream(), + m_ffmpegFile(file) +{ +} + +FFmpegOpenALAudioStream::~FFmpegOpenALAudioStream() +{ +} diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp index cdd362fd37..0bdabc1678 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp @@ -41,7 +41,7 @@ #include "Lib/BaseType.h" #include "OpenALDevice/OpenALAudioManager.h" -//#include "OpenALAudioDevice/OpenALAudioStream.h" +#include "OpenALDevice/OpenALAudioStream.h" #include "OpenALAudioFileCache.h" #include "Common/AudioAffect.h" @@ -55,7 +55,6 @@ #include "Common/GameSounds.h" #include "Common/CRCDebug.h" #include "Common/GlobalData.h" -#include "Common/ScopedMutex.h" #include "GameClient/DebugDisplay.h" #include "GameClient/Drawable.h" @@ -632,7 +631,7 @@ void OpenALAudioManager::pauseAudio(AudioAffect which) AudioRequest *req = (*ait); if( req && req->m_request == AR_Play ) { - req->deleteInstance(); + deleteInstance(req); ait = m_audioRequests.erase(ait); } else @@ -767,54 +766,54 @@ void OpenALAudioManager::playAudioEvent(AudioEventRTS* event) OpenALAudioStream* stream; if (!handleToKill || foundSoundToReplace) { - //stream = new OpenALAudioStream; - //// When we need more data ask FFmpeg for more data. - //stream->setRequireDataCallback([ffmpegFile, stream]() { - // ffmpegFile->decodePacket(); - // }); - // - //// When we receive a frame from FFmpeg, send it to OpenAL. - //ffmpegFile->setFrameCallback([stream](AVFrame* frame, int stream_idx, int stream_type, void* user_data) { - // if (stream_type != AVMEDIA_TYPE_AUDIO) { - // return; - // } - - // DEBUG_LOG(("Received audio frame\n")); - - // AVSampleFormat sampleFmt = static_cast(frame->format); - // const int bytesPerSample = av_get_bytes_per_sample(sampleFmt); - // ALenum format = OpenALAudioManager::getALFormat(frame->ch_layout.nb_channels, bytesPerSample * 8); - // const int frameSize = - // av_samples_get_buffer_size(NULL, frame->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1); - // uint8_t* frameData = frame->data[0]; - - // // We need to interleave the samples if the format is planar - // if (av_sample_fmt_is_planar(static_cast(frame->format))) { - // uint8_t* audioBuffer = static_cast(av_malloc(frameSize)); - - // // Write the samples into our audio buffer - // for (int sample_idx = 0; sample_idx < frame->nb_samples; sample_idx++) - // { - // int byte_offset = sample_idx * bytesPerSample; - // for (int channel_idx = 0; channel_idx < frame->ch_layout.nb_channels; channel_idx++) - // { - // uint8_t* dst = &audioBuffer[byte_offset * frame->ch_layout.nb_channels + channel_idx * bytesPerSample]; - // uint8_t* src = &frame->data[channel_idx][byte_offset]; - // memcpy(dst, src, bytesPerSample); - // } - // } - // stream->bufferData(audioBuffer, frameSize, format, frame->sample_rate); - // av_freep(&audioBuffer); - // } - // else - // stream->bufferData(frameData, frameSize, format, frame->sample_rate); - // }); + stream = new OpenALAudioStream; + // When we need more data ask FFmpeg for more data. + stream->setRequireDataCallback([ffmpegFile, stream]() { + ffmpegFile->decodePacket(); + }); + + // When we receive a frame from FFmpeg, send it to OpenAL. + ffmpegFile->setFrameCallback([stream](AVFrame* frame, int stream_idx, int stream_type, void* user_data) { + if (stream_type != AVMEDIA_TYPE_AUDIO) { + return; + } + + DEBUG_LOG(("Received audio frame\n")); + + AVSampleFormat sampleFmt = static_cast(frame->format); + const int bytesPerSample = av_get_bytes_per_sample(sampleFmt); + ALenum format = OpenALAudioManager::getALFormat(frame->ch_layout.nb_channels, bytesPerSample * 8); + const int frameSize = + av_samples_get_buffer_size(NULL, frame->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1); + uint8_t* frameData = frame->data[0]; + + // We need to interleave the samples if the format is planar + if (av_sample_fmt_is_planar(static_cast(frame->format))) { + uint8_t* audioBuffer = static_cast(av_malloc(frameSize)); + + // Write the samples into our audio buffer + for (int sample_idx = 0; sample_idx < frame->nb_samples; sample_idx++) + { + int byte_offset = sample_idx * bytesPerSample; + for (int channel_idx = 0; channel_idx < frame->ch_layout.nb_channels; channel_idx++) + { + uint8_t* dst = &audioBuffer[byte_offset * frame->ch_layout.nb_channels + channel_idx * bytesPerSample]; + uint8_t* src = &frame->data[channel_idx][byte_offset]; + memcpy(dst, src, bytesPerSample); + } + } + stream->bufferData(audioBuffer, frameSize, format, frame->sample_rate); + av_freep(&audioBuffer); + } + else + stream->bufferData(frameData, frameSize, format, frame->sample_rate); + }); // Decode packets before starting the stream. - //for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) { - // if (!ffmpegFile->decodePacket()) - // break; - //} + for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) { + if (!ffmpegFile->decodePacket()) + break; + } } else { stream = NULL; @@ -1061,7 +1060,7 @@ void OpenALAudioManager::killAudioEventImmediately(AudioHandle audioEvent) AudioRequest *req = (*ait); if( req && req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) { - req->deleteInstance(); + deleteInstance(req); ait = m_audioRequests.erase(ait); return; } @@ -2305,7 +2304,7 @@ void OpenALAudioManager::processRequestList(void) if (!req->m_requiresCheckForSample || checkForSample(req)) { processRequest(req); } - req->deleteInstance(); + deleteInstance(req); it = m_audioRequests.erase(it); } } @@ -2945,7 +2944,7 @@ void* OpenALAudioManager::getHandleForBink(void) { if (!m_binkAudio) { DEBUG_LOG(("Creating Bink audio stream\n")); - //m_binkAudio = NEW OpenALAudioStream; + m_binkAudio = NEW OpenALAudioStream; } return m_binkAudio; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioStream.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioStream.cpp new file mode 100644 index 0000000000..06061c5f01 --- /dev/null +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioStream.cpp @@ -0,0 +1,117 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: OpenALAudioStream.cpp ////////////////////////////////////////////////////////////////////////// +// OpenALAudioStream implementation +// Author: Stephan Vedder, May 2025 +#include "OpenALDevice/OpenALAudioStream.h" +#include "OpenALDevice/OpenALAudioManager.h" + +OpenALAudioStream::OpenALAudioStream() +{ + alGenSources(1, &m_source); + alGenBuffers(AL_STREAM_BUFFER_COUNT, m_buffers); + + // Make stream ignore positioning + alSourcei(m_source, AL_SOURCE_RELATIVE, AL_TRUE); + + DEBUG_LOG(("OpenALAudioStream created: %i\n", m_source)); +} + +OpenALAudioStream::~OpenALAudioStream() +{ + DEBUG_LOG(("OpenALAudioStream freed: %i\n", m_source)); + // Unbind the buffers first + alSourceStop(m_source); + alSourcei(m_source, AL_BUFFER, 0); + alDeleteSources(1, &m_source); + // Now delete the buffers + alDeleteBuffers(AL_STREAM_BUFFER_COUNT, m_buffers); +} + +bool OpenALAudioStream::bufferData(uint8_t *data, size_t data_size, ALenum format, int samplerate) +{ + DEBUG_LOG(("Buffering %zu bytes of data (samplerate: %i, format: %i)\n", data_size, samplerate, format)); + ALint numQueued; + alGetSourcei(m_source, AL_BUFFERS_QUEUED, &numQueued); + if (numQueued >= AL_STREAM_BUFFER_COUNT) { + DEBUG_LOG(("Having too many buffers already queued: %i", numQueued)); + return false; + } + + ALuint ¤tBuffer = m_buffers[m_currentBufferIndex]; + alBufferData(currentBuffer, format, data, data_size, samplerate); + alSourceQueueBuffers(m_source, 1, ¤tBuffer); + m_currentBufferIndex++; + + if (m_currentBufferIndex >= AL_STREAM_BUFFER_COUNT) + m_currentBufferIndex = 0; + + return true; +} + +void OpenALAudioStream::update() +{ + ALint sourceState; + alGetSourcei(m_source, AL_SOURCE_STATE, &sourceState); + + ALint processed; + alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processed); + DEBUG_LOG(("%i buffers have been processed\n", processed)); + while (processed > 0) { + ALuint buffer; + alSourceUnqueueBuffers(m_source, 1, &buffer); + processed--; + } + + ALint numQueued; + alGetSourcei(m_source, AL_BUFFERS_QUEUED, &numQueued); + DEBUG_LOG(("Having %i buffers queued\n", numQueued)); + if (numQueued < AL_STREAM_BUFFER_COUNT && m_requireDataCallback) { + // Ask for more data to be buffered + while (numQueued < AL_STREAM_BUFFER_COUNT) { + m_requireDataCallback(); + numQueued++; + } + } + + if (sourceState == AL_STOPPED) { + play(); + } +} + +void OpenALAudioStream::reset() +{ + DEBUG_LOG(("Resetting stream\n")); + alSourceRewind(m_source); + alSourcei(m_source, AL_BUFFER, 0); + m_currentBufferIndex = 0; +} + +bool OpenALAudioStream::isPlaying() +{ + ALint state; + alGetSourcei(m_source, AL_SOURCE_STATE, &state); + return state == AL_PLAYING; +} \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp index 69b08bf064..dfa96e2294 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp @@ -48,7 +48,7 @@ extern "C" { #ifdef RTS_HAS_OPENAL #include "OpenALDevice/OpenALAudioManager.h" -//#include "OpenALDevice/OpenALAudioStream.h" +#include "OpenALDevice/OpenALAudioStream.h" #endif #include @@ -311,7 +311,7 @@ FFmpegVideoStream::FFmpegVideoStream(FFmpegFile* file) m_ffmpegFile->setFrameCallback(onFrame); m_ffmpegFile->setUserData(this); -#if 0//def RTS_HAS_OPENAL +#ifdef RTS_HAS_OPENAL // Release the audio handle if it's already in use OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->reset(); @@ -321,7 +321,7 @@ FFmpegVideoStream::FFmpegVideoStream(FFmpegFile* file) while (m_good && m_gotFrame == false) m_good = m_ffmpegFile->decodePacket(); - #if 0//def RTS_HAS_OPENAL + #ifdef RTS_HAS_OPENAL // Start audio playback audioStream->play(); #endif @@ -349,7 +349,7 @@ void FFmpegVideoStream::onFrame(AVFrame *frame, int stream_idx, int stream_type, videoStream->m_frame = av_frame_clone(frame); videoStream->m_gotFrame = true; } -#if 0//def RTS_USE_OPENAL +#ifdef RTS_HAS_OPENAL else if (stream_type == AVMEDIA_TYPE_AUDIO) { OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->update(); @@ -394,7 +394,7 @@ void FFmpegVideoStream::onFrame(AVFrame *frame, int stream_idx, int stream_type, void FFmpegVideoStream::update( void ) { -#if 0//def RTS_HAS_OPENAL +#ifdef RTS_HAS_OPENAL // Start audio playback OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); audioStream->play(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 412b0313f3..c826de0394 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -77,7 +77,7 @@ // GLOBALS //////////////////////////////////////////////////////////////////// HINSTANCE ApplicationHInstance = NULL; ///< our application instance HWND ApplicationHWnd = NULL; ///< our application window handle -Bool ApplicationIsWindowed = false; +Bool ApplicationIsWindowed = true; Win32Mouse *TheWin32Mouse= NULL; ///< for the WndProc() only DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows.