mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-22 02:12:45 +01:00
Merge pull request #522 from ShFil119/prepare_sdt_loader_for_ffmpeg
Prepare sdt loader for ffmpeg
This commit is contained in:
commit
2f8ae7fb0b
@ -1,10 +1,11 @@
|
||||
#include "audio/SoundManager.hpp"
|
||||
|
||||
#include "audio/alCheck.hpp"
|
||||
#include "audio/SoundManager.hpp"
|
||||
#include "loaders/LoaderSDT.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
//ab
|
||||
@ -16,6 +17,10 @@ extern "C" {
|
||||
#define av_frame_free avcodec_free_frame
|
||||
#endif
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57,80,100)
|
||||
#define avio_context_free av_freep
|
||||
#endif
|
||||
|
||||
SoundManager::SoundManager() {
|
||||
initializeOpenAL();
|
||||
initializeAVCodec();
|
||||
@ -173,6 +178,243 @@ void SoundManager::SoundSource::loadFromFile(const rwfs::path& filePath) {
|
||||
avformat_close_input(&formatContext);
|
||||
}
|
||||
|
||||
/// Structure for input data
|
||||
struct InputData {
|
||||
uint8_t *ptr = nullptr;
|
||||
size_t size{}; ///< size left in the buffer
|
||||
};
|
||||
|
||||
/// Low level function for copying data from handler (opaque)
|
||||
/// to buffer.
|
||||
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
|
||||
auto* input = reinterpret_cast<InputData*>(opaque);
|
||||
buf_size = FFMIN(buf_size, input->size);
|
||||
/* copy internal data to buf */
|
||||
memcpy(buf, input->ptr, buf_size);
|
||||
input->ptr += buf_size;
|
||||
input->size -= buf_size;
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
void SoundManager::SoundSource::loadSfx(const rwfs::path& path, const size_t& index, const bool asWave) {
|
||||
// Allocate audio frame
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
RW_ERROR("Error allocating the audio frame");
|
||||
return;
|
||||
}
|
||||
|
||||
/// Now we need to prepare "custom" format context
|
||||
/// We need sdt loader for that purpose
|
||||
LoaderSDT sdt{};
|
||||
sdt.load(path / "audio/sfx");
|
||||
|
||||
std::unique_ptr<char[]> raw_sound = sdt.loadToMemory(index, asWave);
|
||||
if (!raw_sound) {
|
||||
av_frame_free(&frame);
|
||||
RW_ERROR("Error loading sound");
|
||||
return;
|
||||
}
|
||||
|
||||
/// Prepare input
|
||||
InputData input{};
|
||||
input.size = sizeof(WaveHeader) + sdt.assetInfo.size;
|
||||
auto inputDataStart = std::make_unique<uint8_t[]>(input.size); /// Store start ptr of data to be able freed memory later
|
||||
input.ptr = inputDataStart.get();
|
||||
|
||||
/// Alocate memory for buffer
|
||||
/// Memory freeded at the end
|
||||
static constexpr size_t ioBufferSize = 4096;
|
||||
auto ioBuffer = static_cast<uint8_t*>(av_malloc(ioBufferSize));
|
||||
|
||||
/// Cast pointer, in order to match required layout for ffmpeg
|
||||
input.ptr = reinterpret_cast<uint8_t*>(raw_sound.get());
|
||||
|
||||
/// Finally prepare our "custom" format context
|
||||
AVIOContext* avioContext = avio_alloc_context(ioBuffer, ioBufferSize, 0, &input, &read_packet, nullptr, nullptr);
|
||||
AVFormatContext* formatContext = avformat_alloc_context();
|
||||
formatContext->pb = avioContext;
|
||||
|
||||
if (avformat_open_input(&formatContext, "nothint", nullptr, nullptr) != 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
RW_ERROR("Error opening audio file (" << index << ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Error finding audio stream info");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the audio stream
|
||||
//AVCodec* codec = nullptr;
|
||||
int streamIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
||||
if (streamIndex < 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Could not find any audio stream in the file ");
|
||||
return;
|
||||
}
|
||||
|
||||
AVStream* audioStream = formatContext->streams[streamIndex];
|
||||
AVCodec* codec = avcodec_find_decoder(audioStream->codecpar->codec_id);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,5,0)
|
||||
AVCodecContext* codecContext = audioStream->codec;
|
||||
codecContext->codec = codec;
|
||||
|
||||
// Open the codec
|
||||
if (avcodec_open2(codecContext, codecContext->codec, nullptr) != 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Couldn't open the audio codec context");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
// Initialize codec context for the decoder.
|
||||
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
|
||||
if (!codecContext) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Couldn't allocate a decoding context.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill the codecCtx with the parameters of the codec used in the read file.
|
||||
if (avcodec_parameters_to_context(codecContext, audioStream->codecpar) != 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
avcodec_close(codecContext);
|
||||
avcodec_free_context(&codecContext);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Couldn't find parametrs for context");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the decoder.
|
||||
if (avcodec_open2(codecContext, codec, nullptr) != 0) {
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
avcodec_close(codecContext);
|
||||
avcodec_free_context(&codecContext);
|
||||
avformat_close_input(&formatContext);
|
||||
RW_ERROR("Couldn't open the audio codec context");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Expose audio metadata
|
||||
channels = static_cast<size_t>(codecContext->channels);
|
||||
sampleRate = sdt.assetInfo.sampleRate;
|
||||
|
||||
// OpenAL only supports mono or stereo, so error on more than 2 channels
|
||||
if(channels > 2) {
|
||||
RW_ERROR("Audio has more than two channels");
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
av_frame_free(&frame);
|
||||
avcodec_close(codecContext);
|
||||
avformat_close_input(&formatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start reading audio packets
|
||||
AVPacket readingPacket;
|
||||
av_init_packet(&readingPacket);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
|
||||
|
||||
while (av_read_frame(formatContext, &readingPacket) == 0) {
|
||||
if (readingPacket.stream_index == audioStream->index) {
|
||||
AVPacket decodingPacket = readingPacket;
|
||||
|
||||
while (decodingPacket.size > 0) {
|
||||
// Decode audio packet
|
||||
int gotFrame = 0;
|
||||
int len = avcodec_decode_audio4(codecContext, frame, &gotFrame, &decodingPacket);
|
||||
|
||||
if (len >= 0 && gotFrame) {
|
||||
// Write samples to audio buffer
|
||||
|
||||
for(size_t i = 0; i < static_cast<size_t>(frame->nb_samples); i++) {
|
||||
// Interleave left/right channels
|
||||
for(size_t channel = 0; channel < channels; channel++) {
|
||||
int16_t sample = reinterpret_cast<int16_t *>(frame->data[channel])[i];
|
||||
data.push_back(sample);
|
||||
}
|
||||
}
|
||||
|
||||
decodingPacket.size -= len;
|
||||
decodingPacket.data += len;
|
||||
}
|
||||
else {
|
||||
decodingPacket.size = 0;
|
||||
decodingPacket.data = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
av_free_packet(&readingPacket);
|
||||
}
|
||||
#else
|
||||
|
||||
while (av_read_frame(formatContext, &readingPacket) == 0) {
|
||||
if (readingPacket.stream_index == audioStream->index) {
|
||||
AVPacket decodingPacket = readingPacket;
|
||||
|
||||
int sendPacket = avcodec_send_packet(codecContext, &decodingPacket);
|
||||
int receiveFrame = 0;
|
||||
|
||||
while ((receiveFrame = avcodec_receive_frame(codecContext, frame)) == 0) {
|
||||
// Decode audio packet
|
||||
|
||||
if (receiveFrame == 0 && sendPacket == 0) {
|
||||
// Write samples to audio buffer
|
||||
|
||||
for(size_t i = 0; i < static_cast<size_t>(frame->nb_samples); i++) {
|
||||
// Interleave left/right channels
|
||||
for(size_t channel = 0; channel < channels; channel++) {
|
||||
int16_t sample = reinterpret_cast<int16_t *>(frame->data[channel])[i];
|
||||
data.push_back(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_unref(&readingPacket);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Cleanup
|
||||
/// Free all data used by the frame.
|
||||
av_frame_free(&frame);
|
||||
|
||||
/// Close the context and free all data associated to it, but not the context itself.
|
||||
avcodec_close(codecContext);
|
||||
|
||||
/// Free the context itself.
|
||||
avcodec_free_context(&codecContext);
|
||||
|
||||
/// Free our custom AVIO.
|
||||
av_free(formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
|
||||
/// We are done here. Close the input.
|
||||
avformat_close_input(&formatContext);
|
||||
}
|
||||
|
||||
SoundManager::SoundBuffer::SoundBuffer() {
|
||||
alCheck(alGenSources(1, &source));
|
||||
alCheck(alGenBuffers(1, &buffer));
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <al.h>
|
||||
#include <alc.h>
|
||||
|
||||
#include <rw/filesystem.hpp>
|
||||
|
||||
class SoundManager {
|
||||
public:
|
||||
SoundManager();
|
||||
@ -43,6 +45,7 @@ private:
|
||||
|
||||
public:
|
||||
void loadFromFile(const rwfs::path& filePath);
|
||||
void loadSfx(const rwfs::path& path, const size_t& index, const bool asWave = true);
|
||||
|
||||
private:
|
||||
std::vector<int16_t> data;
|
||||
|
@ -6,32 +6,9 @@
|
||||
|
||||
#include "rw/debug.hpp"
|
||||
|
||||
LoaderSDT::LoaderSDT() : m_version(GTAIIIVC), m_assetCount(0) {
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char chunkId[4];
|
||||
uint32_t chunkSize;
|
||||
char format[4];
|
||||
struct {
|
||||
char id[4];
|
||||
uint32_t size;
|
||||
uint16_t audioFormat;
|
||||
uint16_t numChannels;
|
||||
uint32_t sampleRate;
|
||||
uint32_t byteRate;
|
||||
uint16_t blockAlign;
|
||||
uint16_t bitsPerSample;
|
||||
} fmt;
|
||||
struct {
|
||||
char id[4];
|
||||
uint32_t size;
|
||||
} data;
|
||||
} WaveHeader;
|
||||
|
||||
bool LoaderSDT::load(const std::string& filename) {
|
||||
const auto sdtName = filename + ".SDT";
|
||||
const auto rawName = filename + ".RAW";
|
||||
bool LoaderSDT::load(const rwfs::path& path) {
|
||||
const auto sdtName = path.string() + ".SDT";
|
||||
const auto rawName = path.string() + ".RAW";
|
||||
|
||||
FILE* fp = fopen(sdtName.c_str(), "rb");
|
||||
if (fp) {
|
||||
@ -52,6 +29,7 @@ bool LoaderSDT::load(const std::string& filename) {
|
||||
m_archive = rawName;
|
||||
return true;
|
||||
} else {
|
||||
RW_ERROR("Error cannot find " << path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -65,8 +43,7 @@ bool LoaderSDT::findAssetInfo(size_t index, LoaderSDTFile& out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* LoaderSDT::loadToMemory(size_t index, bool asWave) {
|
||||
LoaderSDTFile assetInfo;
|
||||
std::unique_ptr<char[]> LoaderSDT::loadToMemory(size_t index, bool asWave) {
|
||||
bool found = findAssetInfo(index, assetInfo);
|
||||
|
||||
if (!found) {
|
||||
@ -78,12 +55,12 @@ char* LoaderSDT::loadToMemory(size_t index, bool asWave) {
|
||||
|
||||
FILE* fp = fopen(rawName.c_str(), "rb");
|
||||
if (fp) {
|
||||
char* raw_data;
|
||||
std::unique_ptr<char[]> raw_data;
|
||||
char* sample_data;
|
||||
if (asWave) {
|
||||
raw_data = new char[sizeof(WaveHeader) + assetInfo.size];
|
||||
raw_data = std::make_unique<char[]>(sizeof(WaveHeader) + assetInfo.size);
|
||||
|
||||
WaveHeader* header = reinterpret_cast<WaveHeader*>(raw_data);
|
||||
auto header = reinterpret_cast<WaveHeader*>(raw_data.get());
|
||||
memcpy(header->chunkId, "RIFF", 4);
|
||||
header->chunkSize = sizeof(WaveHeader) - 8 + assetInfo.size;
|
||||
memcpy(header->format, "WAVE", 4);
|
||||
@ -98,10 +75,10 @@ char* LoaderSDT::loadToMemory(size_t index, bool asWave) {
|
||||
memcpy(header->data.id, "data", 4);
|
||||
header->data.size = assetInfo.size;
|
||||
|
||||
sample_data = raw_data + sizeof(WaveHeader);
|
||||
sample_data = raw_data.get() + sizeof(WaveHeader);
|
||||
} else {
|
||||
raw_data = new char[assetInfo.size];
|
||||
sample_data = raw_data;
|
||||
raw_data = std::make_unique<char[]>(assetInfo.size);
|
||||
sample_data = raw_data.get();
|
||||
}
|
||||
|
||||
fseek(fp, assetInfo.offset, SEEK_SET);
|
||||
@ -118,29 +95,25 @@ char* LoaderSDT::loadToMemory(size_t index, bool asWave) {
|
||||
/// Writes the contents of assetname to filename
|
||||
bool LoaderSDT::saveAsset(size_t index, const std::string& filename,
|
||||
bool asWave) {
|
||||
char* raw_data = loadToMemory(index, asWave);
|
||||
if (!raw_data) return false;
|
||||
auto raw_sound = loadToMemory(index, asWave);
|
||||
if (!raw_sound) return false;
|
||||
|
||||
FILE* dumpFile = fopen(filename.c_str(), "wb");
|
||||
if (dumpFile) {
|
||||
LoaderSDTFile asset;
|
||||
if (findAssetInfo(index, asset)) {
|
||||
fwrite(raw_data, 1, asset.size + (asWave ? sizeof(WaveHeader) : 0),
|
||||
if (findAssetInfo(index, assetInfo)) {
|
||||
fwrite(raw_sound.get(), 1, assetInfo.size + (asWave ? sizeof(WaveHeader) : 0),
|
||||
dumpFile);
|
||||
printf("=> SDT: Saved %zu to disk with filename %s\n", index,
|
||||
filename.c_str());
|
||||
}
|
||||
fclose(dumpFile);
|
||||
|
||||
delete[] raw_data;
|
||||
return true;
|
||||
} else {
|
||||
delete[] raw_data;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the information of an asset by its index
|
||||
const LoaderSDTFile& LoaderSDT::getAssetInfoByIndex(size_t index) const {
|
||||
return m_assets[index];
|
||||
}
|
||||
|
@ -1,11 +1,34 @@
|
||||
#ifndef _LIBRW_LOADERSDT_HPP_
|
||||
#define _LIBRW_LOADERSDT_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <rw/filesystem.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
typedef struct {
|
||||
char chunkId[4];
|
||||
uint32_t chunkSize;
|
||||
char format[4];
|
||||
struct {
|
||||
char id[4];
|
||||
uint32_t size;
|
||||
uint16_t audioFormat;
|
||||
uint16_t numChannels;
|
||||
uint32_t sampleRate;
|
||||
uint32_t byteRate;
|
||||
uint16_t blockAlign;
|
||||
uint16_t bitsPerSample;
|
||||
} fmt;
|
||||
struct {
|
||||
char id[4];
|
||||
uint32_t size;
|
||||
} data;
|
||||
} WaveHeader;
|
||||
|
||||
/// \brief Points to one file within the archive
|
||||
class LoaderSDTFile {
|
||||
public:
|
||||
@ -33,16 +56,18 @@ public:
|
||||
};
|
||||
|
||||
/// Construct
|
||||
LoaderSDT();
|
||||
LoaderSDT() = default;
|
||||
|
||||
/// Destructor
|
||||
~LoaderSDT() = default;
|
||||
|
||||
/// Load the structure of the archive
|
||||
/// Omit the extension in filename
|
||||
bool load(const std::string& filename);
|
||||
bool load(const rwfs::path& path);
|
||||
|
||||
/// Load a file from the archive to memory and pass a pointer to it
|
||||
/// Warning: Please delete[] the memory in the end.
|
||||
/// Warning: Returns NULL (0) if by any reason it can't load the file
|
||||
char* loadToMemory(size_t index, bool asWave = true);
|
||||
/// Warning: Returns nullptr if by any reason it can't load the file
|
||||
std::unique_ptr<char[]> loadToMemory(size_t index, bool asWave = true);
|
||||
|
||||
/// Writes the contents of index to filename
|
||||
bool saveAsset(size_t index, const std::string& filename,
|
||||
@ -58,12 +83,11 @@ public:
|
||||
uint32_t getAssetCount() const;
|
||||
|
||||
Version getVersion() const;
|
||||
|
||||
LoaderSDTFile assetInfo{};
|
||||
private:
|
||||
Version m_version; ///< Version of this SDT archive
|
||||
uint32_t m_assetCount; ///< Number of assets in the current archive
|
||||
Version m_version{GTAIIIVC}; ///< Version of this SDT archive
|
||||
uint32_t m_assetCount{0}; ///< Number of assets in the current archive
|
||||
std::string m_archive; ///< Path to the archive being used (no extension)
|
||||
|
||||
std::vector<LoaderSDTFile> m_assets; ///< Asset info of the archive
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user