Skip to content

Commit

Permalink
Merge pull request #135 from DanielGibson/use-libacm
Browse files Browse the repository at this point in the history
Use upstream libacm 1.3 for ACM audio decoding
  • Loading branch information
kevinbentley authored Apr 21, 2024
2 parents 9e9b713 + 5518131 commit 1fd8876
Show file tree
Hide file tree
Showing 8 changed files with 1,236 additions and 1,096 deletions.
3 changes: 3 additions & 0 deletions .clang-format-ignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# 3rd party libs
lib/debugbreak.h
libacm/libacm.h
libacm/decode.c
libacm/util.c
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,37 @@ cmake_minimum_required(VERSION 3.19)

project(Descent3 VERSION 1.5.500)

if(NOT MSVC) # GCC/clang or compatible, hopefully
option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored compiler warnings/errors (GCC/Clang only; esp. useful with ninja)." OFF)
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(NOT MSVC)
# check if this is some kind of clang (Clang, AppleClang, whatever)
# (convert compiler ID to lowercase so we match Clang, clang, AppleClang etc, regardless of case)
string(TOLOWER ${CMAKE_CXX_COMPILER_ID} compiler_id_lower)
if(compiler_id_lower MATCHES ".*clang.*")
message(STATUS "Compiler \"${CMAKE_CXX_COMPILER_ID}\" detected as some kind of clang")
set(D3_COMPILER_IS_CLANG TRUE)
set(D3_COMPILER_IS_GCC_OR_CLANG TRUE)
elseif(CMAKE_COMPILER_IS_GNUCC)
set(D3_COMPILER_IS_GCC_OR_CLANG TRUE)
endif()
unset(compiler_id_lower)

if(FORCE_COLORED_OUTPUT)
if(CMAKE_COMPILER_IS_GNUCC)
add_compile_options (-fdiagnostics-color=always)
elseif(D3_COMPILER_IS_CLANG)
add_compile_options (-fcolor-diagnostics)
endif ()
endif ()
endif()

if(UNIX)
set(D3_GAMEDIR "~/Descent3/")

Expand Down
5 changes: 0 additions & 5 deletions lib/Adecode.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ typedef sint32 (*ReadDataFunction)(void *pData, void *pBuffer, unsigned int amou
IAudioDecoder *CreateDecoder(ReadDataFunction reader, void *pData, uint32 &numChannels, uint32 &sampleRate,
uint32 &sampleCount);

// Optional interface for supplying your own malloc and free functions
// Default is to use standard malloc and free.
typedef void *(*MemoryAllocFunc)(uint32 size);
typedef void (*MemoryFreeFunc)(void *p);
void RegisterMemoryFunctions(MemoryAllocFunc memAlloc, MemoryFreeFunc memFree);
} // namespace AudioDecoder

#endif
9 changes: 7 additions & 2 deletions libacm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
set(HEADERS)
set(CPPS
aencode.cpp
libacm.cpp)
adecode.cpp)

add_library(libacm STATIC ${HEADERS} ${CPPS})
# these are the relevant source files from upstream libacm (https://github.com/markokr/libacm/)
set(LIB_SRC
decode.c
libacm.h)

add_library(libacm STATIC ${HEADERS} ${CPPS} ${LIB_SRC})
163 changes: 163 additions & 0 deletions libacm/adecode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* 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 <http://www.gnu.org/licenses/>.
*/

#include "Adecode.h"

#include "libacm.h"

using namespace AudioDecoder;

namespace {

class InternalAudioDecoder : public IAudioDecoder {
public:
InternalAudioDecoder(ReadDataFunction readerFunction, void *pReaderData);
~InternalAudioDecoder();

// Initialize the decoder
bool Initialize();

// Read data from the audio decoder.
// pBuffer: The buffer to receive the data into
// amount: How much data to read
// Returns the number of bytes read - zero when we're at the end of the file
uint32 Read(void *pBuffer, uint32 amount);

ACMStream *m_acm = nullptr;
ReadDataFunction m_readerFunction;
void *m_pReaderData;
};

/**************************************************************/
/* Construction */
/**************************************************************/

int AcmReadFunc(void *ptr, int size, int n, void *datasrc) {
InternalAudioDecoder *iad = reinterpret_cast<InternalAudioDecoder *>(datasrc);
int ret =
iad->m_readerFunction(iad->m_pReaderData, ptr, (unsigned int)size * n);
// ret < 0: error, ret == 0: EOF, ret > 0: read ret bytes of data
// apparently acm_io_callbacks::read() expects pretty much the same behavior,
// except that for > 0 it's not number of bytes but number of items (like in
// fread())
if (ret > 0) {
ret /= size;
}
return ret;
}

InternalAudioDecoder::InternalAudioDecoder(ReadDataFunction readerFunction,
void *pReaderData)
: m_readerFunction(readerFunction), m_pReaderData(pReaderData) {}

// Initialize the decoder
bool InternalAudioDecoder::Initialize() {

acm_io_callbacks io = {
AcmReadFunc}; // set the read function, the others are optional

int force_channels =
0; // 0 = let libacm figure out how many channels the file has
// TODO: the old libacm.cpp was more optimistic about the numbers of channel
// from the file header
// than libacm's decode.c, which assumes that channels are always >= 2
// (unless it's WAVC instead of "plain ACM"), i.e. that a file header
// specifying 1 is wrong. If it turns out that we really have ACM files
// with just one channel (not unusual for ingame sounds?), we might have
// to either patch acm_open_decoder() or somehow detect the number of
// channels here and set force_channels accordingly
int ret = acm_open_decoder(&m_acm, this, io, force_channels);
return ret == ACM_OK;
}

/**************************************************************/
/* Destruction */
/**************************************************************/

InternalAudioDecoder::~InternalAudioDecoder() {
if (m_acm != nullptr)
acm_close(m_acm);
}

/**************************************************************/
/* Reading */
/**************************************************************/

// Read data from the audio decoder.
// pBuffer: The buffer to receive the data into
// amount: How much data to read
// Returns the number of bytes read - zero when we're at the end of the file
uint32 InternalAudioDecoder::Read(void *pBuffer, uint32 amount) {
const int bigendianp = 0; // we want little endian samples - TODO: or only on little endian platforms?
const int wordlen = 2; // the only supported value
const int sgned = 1; // we want signed samples
uint32 totalBytesRead = 0;
uint8 *pBuf = reinterpret_cast<uint8 *>(pBuffer);

while (totalBytesRead < amount) {
int numRead = acm_read(m_acm, pBuf, amount - totalBytesRead, bigendianp, wordlen, sgned);
// numRead < 0: error, numRead == 0: EOF, numRead > 0: amount of bytes read
if (numRead <= 0)
break;
totalBytesRead += numRead;
pBuf += numRead;
}

return totalBytesRead;
}

} // namespace

/**************************************************************/
/* Interface Functions */
/**************************************************************/

// Create an audio decoder
// You supply a function for reading bytes from the compressed data via a
// void* pData handle, and the handle itself (typically a FILE *).
// Create_AudioDecoder returns a new AudioDecoder which can be used to
// read uncompressed decoded data from the compressed stream,
// and also returns the number of channels (1 or 2), the sample rate
// (e.g. 22050), and the number of samples contained in the compressed file
// (in case you want to pre-allocate a buffer to load them all into memory).
IAudioDecoder *AudioDecoder::CreateDecoder(ReadDataFunction readerFunction,
void *pReaderData,
uint32 &numChannels,
uint32 &sampleRate,
uint32 &sampleCount) {
// allocate our decoder
InternalAudioDecoder *pDecoder =
new InternalAudioDecoder(readerFunction, pReaderData);
if (pDecoder == nullptr)
return nullptr;

// initialize
if (!pDecoder->Initialize()) {
// Failed
delete pDecoder;
return nullptr;
}

// extract the header information for the caller
numChannels = pDecoder->m_acm->info.channels;
sampleRate = pDecoder->m_acm->info.rate;
sampleCount = pDecoder->m_acm->total_values;

// return the decoder back to the user
return pDecoder;
}
Loading

0 comments on commit 1fd8876

Please sign in to comment.