cellMusicDecode: initial implementation

Implements the basic functionality of cellMusicDecode.
Works with Space Invaders (if you add the list selection from the other PR).
Probably fixes SSX custom music.
This commit is contained in:
Megamouse 2022-03-05 14:20:07 +01:00
parent f61ee85f80
commit aafd74f9ea
6 changed files with 569 additions and 53 deletions

View file

@ -7,12 +7,18 @@
#pragma warning(push, 0)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
}
constexpr int averror_eof = AVERROR_EOF; // workaround for old-style-cast error
#ifdef _MSC_VER
#pragma warning(pop)
#else
@ -156,4 +162,257 @@ namespace utils
return { true, std::move(info) };
}
struct scoped_av
{
AVFormatContext* format = nullptr;
AVCodec* codec = nullptr;
AVCodecContext* context = nullptr;
AVFrame* frame = nullptr;
SwrContext* swr = nullptr;
~scoped_av()
{
// Clean up
if (frame)
av_frame_free(&frame);
if (swr)
swr_free(&swr);
if (context)
avcodec_close(context);
if (codec)
av_free(codec);
if (format)
avformat_free_context(format);
}
};
audio_decoder::audio_decoder()
{
}
audio_decoder::~audio_decoder()
{
stop();
}
void audio_decoder::set_path(const std::string& path)
{
m_path = path;
}
void audio_decoder::set_swap_endianness(bool swapped)
{
m_swap_endianness = swapped;
}
void audio_decoder::stop()
{
if (m_thread)
{
auto& thread = *m_thread;
thread = thread_state::aborting;
thread();
}
has_error = false;
m_size = 0;
timestamps_ms.clear();
data.clear();
}
void audio_decoder::decode()
{
stop();
m_thread = std::make_unique<named_thread<std::function<void()>>>("Music Decode Thread", [this, path = m_path]()
{
scoped_av av;
// Get format from audio file
av.format = avformat_alloc_context();
if (int err = avformat_open_input(&av.format, path.c_str(), nullptr, nullptr); err < 0)
{
media_log.error("audio_decoder: Could not open file '%s'. Error: %d='%s'", path, err, error_to_string(err));
has_error = true;
return;
}
if (int err = avformat_find_stream_info(av.format, nullptr); err < 0)
{
media_log.error("audio_decoder: Could not retrieve stream info from file '%s'. Error: %d='%s'", path, err, error_to_string(err));
has_error = true;
return;
}
// Find the first audio stream
AVStream* stream = nullptr;
unsigned int stream_index;
for (stream_index = 0; stream_index < av.format->nb_streams; stream_index++)
{
if (av.format->streams[stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
stream = av.format->streams[stream_index];
break;
}
}
if (!stream)
{
media_log.error("audio_decoder: Could not retrieve audio stream from file '%s'", path);
has_error = true;
return;
}
// Find decoder
av.codec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!av.codec)
{
media_log.error("audio_decoder: Failed to find decoder for stream #%u in file '%s'", stream_index, path);
has_error = true;
return;
}
// Allocate context
av.context = avcodec_alloc_context3(av.codec);
if (!av.context)
{
media_log.error("audio_decoder: Failed to allocate context for stream #%u in file '%s'", stream_index, path);
has_error = true;
return;
}
// Open decoder
if (int err = avcodec_open2(av.context, av.codec, nullptr); err < 0)
{
media_log.error("audio_decoder: Failed to open decoder for stream #%u in file '%s'. Error: %d='%s'", stream_index, path, err, error_to_string(err));
has_error = true;
return;
}
// Prepare resampler
av.swr = swr_alloc();
if (!av.swr)
{
media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path);
has_error = true;
return;
}
const int dst_channels = 2;
const u64 dst_channel_layout = AV_CH_LAYOUT_STEREO;
const AVSampleFormat dst_format = AV_SAMPLE_FMT_FLT;
int set_err = 0;
if ((set_err = av_opt_set_int(av.swr, "in_channel_count", stream->codecpar->channels, 0)) ||
(set_err = av_opt_set_int(av.swr, "out_channel_count", dst_channels, 0)) ||
(set_err = av_opt_set_channel_layout(av.swr, "in_channel_layout", stream->codecpar->channel_layout, 0)) ||
(set_err = av_opt_set_channel_layout(av.swr, "out_channel_layout", dst_channel_layout, 0)) ||
(set_err = av_opt_set_int(av.swr, "in_sample_rate", stream->codecpar->sample_rate, 0)) ||
(set_err = av_opt_set_int(av.swr, "out_sample_rate", sample_rate, 0)) ||
(set_err = av_opt_set_sample_fmt(av.swr, "in_sample_fmt", static_cast<AVSampleFormat>(stream->codecpar->format), 0)) ||
(set_err = av_opt_set_sample_fmt(av.swr, "out_sample_fmt", dst_format, 0)))
{
media_log.error("audio_decoder: Failed to set resampler options: Error: %d='%s'", set_err, error_to_string(set_err));
has_error = true;
return;
}
if (int err = swr_init(av.swr); err < 0 || !swr_is_initialized(av.swr))
{
media_log.error("audio_decoder: Resampler has not been properly initialized: %d='%s'", err, error_to_string(err));
has_error = true;
return;
}
// Prepare to read data
AVPacket packet;
av_init_packet(&packet);
av.frame = av_frame_alloc();
if (!av.frame)
{
media_log.error("audio_decoder: Error allocating the frame");
has_error = true;
return;
}
duration_ms = stream->duration / 1000;
// Iterate through frames
while (thread_ctrl::state() != thread_state::aborting && av_read_frame(av.format, &packet) >= 0)
{
if (int err = avcodec_send_packet(av.context, &packet); err < 0)
{
media_log.error("audio_decoder: Queuing error: %d='%s'", err, error_to_string(err));
has_error = true;
return;
}
while (thread_ctrl::state() != thread_state::aborting)
{
if (int err = avcodec_receive_frame(av.context, av.frame); err < 0)
{
if (err == AVERROR(EAGAIN) || err == averror_eof)
break;
media_log.error("audio_decoder: Decoding error: %d='%s'", err, error_to_string(err));
has_error = true;
return;
}
// Resample frames
u8* buffer;
const int align = 1;
const int buffer_size = av_samples_alloc(&buffer, nullptr, dst_channels, av.frame->nb_samples, dst_format, align);
if (buffer_size < 0)
{
media_log.error("audio_decoder: Error allocating buffer: %d='%s'", buffer_size, error_to_string(buffer_size));
has_error = true;
return;
}
const int frame_count = swr_convert(av.swr, &buffer, av.frame->nb_samples, const_cast<const uint8_t**>(av.frame->data), av.frame->nb_samples);
if (frame_count < 0)
{
media_log.error("audio_decoder: Error converting frame: %d='%s'", frame_count, error_to_string(frame_count));
has_error = true;
if (buffer)
av_free(buffer);
return;
}
// Append resampled frames to data
{
std::scoped_lock lock(m_mtx);
data.resize(m_size + buffer_size);
if (m_swap_endianness)
{
// The format is float 32bit per channel.
const auto write_byteswapped = [](const void* src, void* dst) -> void
{
*static_cast<f32*>(dst) = *static_cast<const be_t<f32>*>(src);
};
for (size_t i = 0; i < (buffer_size - sizeof(f32)); i += sizeof(f32))
{
write_byteswapped(buffer + i, data.data() + m_size + i);
}
}
else
{
memcpy(&data[m_size], buffer, buffer_size);
}
const u32 timestamp_ms = stream->time_base.den ? (1000 * av.frame->best_effort_timestamp * stream->time_base.num) / stream->time_base.den : 0;
timestamps_ms.push_back({m_size, timestamp_ms});
m_size += buffer_size;
}
if (buffer)
av_free(buffer);
media_log.trace("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp);
}
}
});
}
}