Merge pull request #28 from Laupetin/feature/t6-sound-dumping

T6 sound data dumping
This commit is contained in:
Jan 2023-10-26 23:14:01 +02:00 committed by GitHub
commit 1c56bf48c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 240 additions and 124 deletions

View File

@ -38,8 +38,8 @@ struct SoundAssetBankEntry
unsigned int size; unsigned int size;
unsigned int offset; unsigned int offset;
unsigned int frameCount; unsigned int frameCount;
char frameRateIndex; unsigned char frameRateIndex;
char channelCount; unsigned char channelCount;
char looping; unsigned char looping;
char format; unsigned char format;
}; };

View File

@ -1,6 +1,7 @@
#include "AssetDumperLoadedSound.h" #include "AssetDumperLoadedSound.h"
#include "Sound/WavTypes.h" #include "Sound/WavTypes.h"
#include "Sound/WavWriter.h"
using namespace IW4; using namespace IW4;
@ -11,43 +12,16 @@ bool AssetDumperLoadedSound::ShouldDump(XAssetInfo<LoadedSound>* asset)
void AssetDumperLoadedSound::DumpWavPcm(AssetDumpingContext& context, const LoadedSound* asset, std::ostream& stream) void AssetDumperLoadedSound::DumpWavPcm(AssetDumpingContext& context, const LoadedSound* asset, std::ostream& stream)
{ {
const auto riffMasterChunkSize = sizeof(WAV_CHUNK_ID_RIFF) const WavWriter writer(stream);
+ sizeof(uint32_t)
+ sizeof(WAV_WAVE_ID)
+ sizeof(WavChunkHeader)
+ sizeof(WavFormatChunkPcm)
+ sizeof(WavChunkHeader)
+ sizeof(asset->sound.info.data_len);
stream.write(reinterpret_cast<const char*>(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF)); const WavMetaData metaData{
stream.write(reinterpret_cast<const char*>(&riffMasterChunkSize), sizeof(riffMasterChunkSize)); static_cast<unsigned>(asset->sound.info.channels),
stream.write(reinterpret_cast<const char*>(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID)); static_cast<unsigned>(asset->sound.info.rate),
static_cast<unsigned>(asset->sound.info.bits)
const WavChunkHeader formatChunkHeader
{
WAV_CHUNK_ID_FMT,
sizeof(WavFormatChunkPcm)
}; };
stream.write(reinterpret_cast<const char*>(&formatChunkHeader), sizeof(formatChunkHeader));
WavFormatChunkPcm formatChunk writer.WritePcmHeader(metaData, asset->sound.info.data_len);
{ writer.WritePcmData(asset->sound.data, asset->sound.info.data_len);
WavFormat::PCM,
static_cast<uint16_t>(asset->sound.info.channels),
asset->sound.info.rate,
asset->sound.info.rate * asset->sound.info.channels * asset->sound.info.bits / 8,
static_cast<uint16_t>(asset->sound.info.block_size),
static_cast<uint16_t>(asset->sound.info.bits)
};
stream.write(reinterpret_cast<const char*>(&formatChunk), sizeof(formatChunk));
const WavChunkHeader dataChunkHeader
{
WAV_CHUNK_ID_DATA,
asset->sound.info.data_len
};
stream.write(reinterpret_cast<const char*>(&dataChunkHeader), sizeof(dataChunkHeader));
stream.write(asset->sound.data, asset->sound.info.data_len);
} }
void AssetDumperLoadedSound::DumpAsset(AssetDumpingContext& context, XAssetInfo<LoadedSound>* asset) void AssetDumperLoadedSound::DumpAsset(AssetDumpingContext& context, XAssetInfo<LoadedSound>* asset)

View File

@ -2,18 +2,19 @@
#include <fstream> #include <fstream>
#include <filesystem> #include <filesystem>
#include <unordered_map> #include <unordered_set>
#include "Utils/ClassUtils.h" #include "Utils/ClassUtils.h"
#include "Csv/CsvStream.h" #include "Csv/CsvStream.h"
#include "ObjContainer/SoundBank/SoundBank.h" #include "ObjContainer/SoundBank/SoundBank.h"
#include "Sound/WavWriter.h"
using namespace T6; using namespace T6;
namespace fs = std::filesystem; namespace fs = std::filesystem;
class AssetDumperSndBank::Internal namespace
{ {
inline static const std::string ALIAS_HEADERS[] const std::string ALIAS_HEADERS[]
{ {
"# name", "# name",
"# file", "# file",
@ -80,66 +81,54 @@ class AssetDumperSndBank::Internal
"# snapshot", "# snapshot",
}; };
const std::string PREFIXES_TO_DROP[]
{
"raw/",
"devraw/",
};
constexpr size_t FRAME_RATE_FOR_INDEX[]
{
8000,
12000,
16000,
24000,
32000,
44100,
48000,
96000,
192000
};
}
class AssetDumperSndBank::Internal
{
AssetDumpingContext& m_context; AssetDumpingContext& m_context;
static std::string GetExtensionForFormat(const snd_asset_format format) _NODISCARD std::string GetAssetFilename(std::string outputFileName, const std::string& extension) const
{
switch (format)
{
case SND_ASSET_FORMAT_MP3:
return ".mp3";
case SND_ASSET_FORMAT_FLAC:
return ".flac";
case SND_ASSET_FORMAT_PCMS16:
case SND_ASSET_FORMAT_PCMS24:
case SND_ASSET_FORMAT_PCMS32:
case SND_ASSET_FORMAT_IEEE:
case SND_ASSET_FORMAT_XMA4:
case SND_ASSET_FORMAT_MSADPCM:
case SND_ASSET_FORMAT_WMA:
case SND_ASSET_FORMAT_WIIUADPCM:
case SND_ASSET_FORMAT_MPC:
std::cout << "Unsupported sound format " << format << std::endl;
return std::string();
default:
assert(false);
std::cout << "Unknown sound format " << format << std::endl;
return std::string();
}
}
_NODISCARD std::string GetAssetFilename(const std::string& outputFileName, const SoundAssetBankEntry& entry) const
{ {
fs::path assetPath(m_context.m_base_path); fs::path assetPath(m_context.m_base_path);
fs::path assetName(outputFileName); std::replace(outputFileName.begin(), outputFileName.end(), '\\', '/');
auto firstPart = true; for (const auto& droppedPrefix : PREFIXES_TO_DROP)
for (const auto& part : assetName)
{ {
if (firstPart) if (outputFileName.rfind(droppedPrefix, 0) != std::string::npos)
{ {
firstPart = false; outputFileName.erase(0, droppedPrefix.size());
if (part.string() == "raw" break;
|| part.string() == "devraw") }
continue;
} }
assetPath.append(part.string()); assetPath.append(outputFileName);
}
const auto extension = GetExtensionForFormat(static_cast<snd_asset_format>(entry.format));
if (!extension.empty()) if (!extension.empty())
assetPath.concat(extension); assetPath.concat(extension);
return assetPath.string(); return assetPath.string();
} }
_NODISCARD std::unique_ptr<std::ostream> OpenAssetOutputFile(const std::string& outputFileName, const SoundAssetBankEntry& entry) const _NODISCARD std::unique_ptr<std::ostream> OpenAssetOutputFile(const std::string& outputFileName, const std::string& extension) const
{ {
fs::path assetPath(GetAssetFilename(outputFileName, entry)); fs::path assetPath(GetAssetFilename(outputFileName, extension));
auto assetDir(assetPath); auto assetDir(assetPath);
assetDir.remove_filename(); assetDir.remove_filename();
@ -156,7 +145,7 @@ class AssetDumperSndBank::Internal
return nullptr; return nullptr;
} }
std::unique_ptr<std::ostream> OpenAliasOutputFile(const SndBank* sndBank) const static std::unique_ptr<std::ostream> OpenAliasOutputFile(const SndBank* sndBank)
{ {
return nullptr; return nullptr;
} }
@ -171,7 +160,7 @@ class AssetDumperSndBank::Internal
stream.NextRow(); stream.NextRow();
} }
void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias)
{ {
// name // name
stream.WriteColumn(alias->name); stream.WriteColumn(alias->name);
@ -247,7 +236,7 @@ class AssetDumperSndBank::Internal
// "# snapshot", // "# snapshot",
} }
void DumpSndBankAliases(const SndBank* sndBank, std::unordered_map<unsigned, std::string>& aliasFiles) static void DumpSndBankAliases(const SndBank* sndBank)
{ {
const auto outputFile = OpenAliasOutputFile(sndBank); const auto outputFile = OpenAliasOutputFile(sndBank);
@ -267,28 +256,60 @@ class AssetDumperSndBank::Internal
{ {
const auto& alias = aliasList.head[j]; const auto& alias = aliasList.head[j];
WriteAliasToFile(csvStream, &alias); WriteAliasToFile(csvStream, &alias);
if (alias.assetId && alias.assetFileName)
aliasFiles[alias.assetId] = alias.assetFileName;
} }
} }
} }
void DumpSoundData(std::unordered_map<unsigned, std::string>& aliasFiles) const static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId)
{ {
for (const auto& [id, filename] : aliasFiles)
{
auto foundEntry = false;
for (const auto* soundBank : SoundBank::Repository) for (const auto* soundBank : SoundBank::Repository)
{ {
auto soundFile = soundBank->GetEntryStream(id); auto soundFile = soundBank->GetEntryStream(assetId);
if (soundFile.IsOpen()) if (soundFile.IsOpen())
return soundFile;
}
return {};
}
void DumpSoundFilePcm(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const unsigned bitsPerSample) const
{ {
auto outFile = OpenAssetOutputFile(filename, soundFile.m_entry); const auto outFile = OpenAssetOutputFile(assetFileName, ".wav");
if (!outFile) if (!outFile)
{ {
std::cout << "Failed to open sound outputfile: \"" << filename << "\"" << std::endl; std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n";
break; return;
}
const WavWriter writer(*outFile);
if (soundFile.m_entry.frameRateIndex >= std::extent_v<decltype(FRAME_RATE_FOR_INDEX)>)
return;
const WavMetaData metaData{
soundFile.m_entry.channelCount,
FRAME_RATE_FOR_INDEX[soundFile.m_entry.frameRateIndex],
bitsPerSample
};
writer.WritePcmHeader(metaData, soundFile.m_entry.size);
while (!soundFile.m_stream->eof())
{
char buffer[2048];
soundFile.m_stream->read(buffer, sizeof(buffer));
const auto readSize = soundFile.m_stream->gcount();
outFile->write(buffer, readSize);
}
}
void DumpSoundFilePassthrough(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const std::string& extension) const
{
const auto outFile = OpenAssetOutputFile(assetFileName, extension);
if (!outFile)
{
std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n";
return;
} }
while (!soundFile.m_stream->eof()) while (!soundFile.m_stream->eof())
@ -298,26 +319,73 @@ class AssetDumperSndBank::Internal
const auto readSize = soundFile.m_stream->gcount(); const auto readSize = soundFile.m_stream->gcount();
outFile->write(buffer, readSize); outFile->write(buffer, readSize);
} }
}
foundEntry = true; void DumpSndAlias(const SndAlias& alias) const
{
const auto soundFile = FindSoundDataInSoundBanks(alias.assetId);
if (soundFile.IsOpen())
{
const auto format = static_cast<snd_asset_format>(soundFile.m_entry.format);
switch (format)
{
case SND_ASSET_FORMAT_PCMS16:
DumpSoundFilePcm(alias.assetFileName, soundFile, 16u);
break;
case SND_ASSET_FORMAT_FLAC:
DumpSoundFilePassthrough(alias.assetFileName, soundFile, ".flac");
break;
case SND_ASSET_FORMAT_PCMS24:
case SND_ASSET_FORMAT_PCMS32:
case SND_ASSET_FORMAT_IEEE:
case SND_ASSET_FORMAT_XMA4:
case SND_ASSET_FORMAT_MSADPCM:
case SND_ASSET_FORMAT_WMA:
case SND_ASSET_FORMAT_WIIUADPCM:
case SND_ASSET_FORMAT_MPC:
std::cerr << "Cannot dump sound (Unsupported sound format " << format << "): \"" << alias.assetFileName << "\"\n";
break;
default:
assert(false);
std::cerr << "Cannot dump sound (Unknown sound format " << format << "): \"" << alias.assetFileName << "\"\n";
break; break;
} }
} }
else
if (!foundEntry)
{ {
std::cout << "Could not find data for sound \"" << filename << "\"" << std::endl; std::cerr << "Could not find data for sound \"" << alias.assetFileName << "\"\n";
}
}
void DumpSoundData(const SndBank* sndBank) const
{
std::unordered_set<unsigned> dumpedAssets;
for (auto i = 0u; i < sndBank->aliasCount; i++)
{
const auto& aliasList = sndBank->alias[i];
for (auto j = 0; j < aliasList.count; j++)
{
const auto& alias = aliasList.head[j];
if (alias.assetId && alias.assetFileName && dumpedAssets.find(alias.assetId) == dumpedAssets.end())
{
DumpSndAlias(alias);
dumpedAssets.emplace(alias.assetId);
}
} }
} }
} }
void DumpSndBank(const XAssetInfo<SndBank>* sndBankInfo) void DumpSndBank(const XAssetInfo<SndBank>* sndBankInfo) const
{ {
const auto* sndBank = sndBankInfo->Asset(); const auto* sndBank = sndBankInfo->Asset();
std::unordered_map<unsigned int, std::string> aliasMappings; DumpSndBankAliases(sndBank);
DumpSndBankAliases(sndBank, aliasMappings); DumpSoundData(sndBank);
DumpSoundData(aliasMappings);
} }
public: public:
@ -326,7 +394,7 @@ public:
{ {
} }
void DumpPool(AssetPool<SndBank>* pool) void DumpPool(AssetPool<SndBank>* pool) const
{ {
for (const auto* assetInfo : *pool) for (const auto* assetInfo : *pool)
{ {
@ -340,6 +408,6 @@ public:
void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool) void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool)
{ {
Internal internal(context); const Internal internal(context);
internal.DumpPool(pool); internal.DumpPool(pool);
} }

View File

@ -0,0 +1,53 @@
#include "WavWriter.h"
#include "Sound/WavTypes.h"
WavWriter::WavWriter(std::ostream& stream)
: m_stream(stream)
{
}
void WavWriter::WritePcmHeader(const WavMetaData& metaData, const size_t dataLen) const
{
constexpr auto riffMasterChunkSize = sizeof(WAV_CHUNK_ID_RIFF)
+ sizeof(uint32_t)
+ sizeof(WAV_WAVE_ID)
+ sizeof(WavChunkHeader)
+ sizeof(WavFormatChunkPcm)
+ sizeof(WavChunkHeader)
+ sizeof(dataLen);
m_stream.write(reinterpret_cast<const char*>(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF));
m_stream.write(reinterpret_cast<const char*>(&riffMasterChunkSize), sizeof(riffMasterChunkSize));
m_stream.write(reinterpret_cast<const char*>(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID));
constexpr WavChunkHeader formatChunkHeader
{
WAV_CHUNK_ID_FMT,
sizeof(WavFormatChunkPcm)
};
m_stream.write(reinterpret_cast<const char*>(&formatChunkHeader), sizeof(formatChunkHeader));
const WavFormatChunkPcm formatChunk
{
WavFormat::PCM,
static_cast<uint16_t>(metaData.channelCount),
metaData.samplesPerSec,
metaData.samplesPerSec * metaData.channelCount * metaData.bitsPerSample / 8,
static_cast<uint16_t>(metaData.channelCount * (metaData.bitsPerSample / 8)),
static_cast<uint16_t>(metaData.bitsPerSample)
};
m_stream.write(reinterpret_cast<const char*>(&formatChunk), sizeof(formatChunk));
const WavChunkHeader dataChunkHeader
{
WAV_CHUNK_ID_DATA,
dataLen
};
m_stream.write(reinterpret_cast<const char*>(&dataChunkHeader), sizeof(dataChunkHeader));
}
void WavWriter::WritePcmData(const void* data, const size_t dataLen) const
{
m_stream.write(static_cast<const char*>(data), dataLen);
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <ostream>
struct WavMetaData
{
unsigned channelCount;
unsigned samplesPerSec;
unsigned bitsPerSample;
};
class WavWriter
{
public:
explicit WavWriter(std::ostream& stream);
void WritePcmHeader(const WavMetaData& metaData, size_t dataLen) const;
void WritePcmData(const void* data, size_t dataLen) const;
private:
std::ostream& m_stream;
};