1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 15:02:34 +02:00

Merge pull request #522 from ShFil119/prepare_sdt_loader_for_ffmpeg

Prepare sdt loader for ffmpeg
This commit is contained in:
Daniel Evans 2018-07-20 21:24:11 +01:00 committed by GitHub
commit 2f8ae7fb0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 296 additions and 54 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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];
}

View File

@ -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
};