mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-04-06 23:15:18 +00:00
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:
parent
f61ee85f80
commit
aafd74f9ea
6 changed files with 569 additions and 53 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue