More XMA decoder cleanup.

This commit is contained in:
gibbed 2015-06-21 08:33:49 -05:00
parent 08acb6bc1a
commit 75ef95b1b8
3 changed files with 74 additions and 58 deletions

View File

@ -17,6 +17,7 @@
extern "C" { extern "C" {
#include "libavcodec/avcodec.h" #include "libavcodec/avcodec.h"
#include "libavutil/channel_layout.h"
} }
// Credits for most of this code goes to: // Credits for most of this code goes to:
@ -26,9 +27,11 @@ namespace xe {
namespace apu { namespace apu {
XmaContext::XmaContext() XmaContext::XmaContext()
: guest_ptr_(0) : id_(0)
, guest_ptr_(0)
, is_allocated_(false) , is_allocated_(false)
, is_enabled_(false) , is_enabled_(false)
, decoding_packet_(false)
, codec_(nullptr) , codec_(nullptr)
, context_(nullptr) , context_(nullptr)
, decoded_frame_(nullptr) , decoded_frame_(nullptr)
@ -36,9 +39,6 @@ XmaContext::XmaContext()
XmaContext::~XmaContext() { XmaContext::~XmaContext() {
if (context_) { if (context_) {
if (context_->extradata) {
delete [] context_->extradata;
}
if (avcodec_is_open(context_)) { if (avcodec_is_open(context_)) {
avcodec_close(context_); avcodec_close(context_);
} }
@ -85,21 +85,20 @@ int XmaContext::Setup(uint32_t id, Memory* memory, uint32_t guest_ptr) {
// Initialize these to 0. They'll actually be set later. // Initialize these to 0. They'll actually be set later.
context_->channels = 0; context_->channels = 0;
context_->sample_rate = 0; context_->sample_rate = 0;
context_->block_align = XMA_CONTEXT_DATA::kBytesPerPacket; context_->block_align = kBytesPerPacket;
// Extra data passed to the decoder // Extra data passed to the decoder
context_->extradata_size = 18; std::memset(&extra_data_, 0, sizeof(extra_data_));
context_->extradata = new uint8_t[context_->extradata_size]; extra_data_.bits_per_sample = 16;
std::memset(context_->extradata, 0, context_->extradata_size); extra_data_.channel_mask = AV_CH_FRONT_RIGHT;
extra_data_.decode_flags = 0x10D6;
*(short *)(context_->extradata) = 0x10; // bits per sample context_->extradata_size = sizeof(extra_data_);
*(int *)(context_->extradata + 2) = 1; // channel mask context_->extradata = (uint8_t*)&extra_data_;
*(short *)(context_->extradata + 14) = 0x10D6; // decode flags
// Current frame stuff whatever // Current frame stuff whatever
// samples per frame * 2 max channels * output bytes // samples per frame * 2 max channels * output bytes
current_frame_ = current_frame_ = new uint8_t[kSamplesPerFrame * 2 * 2];
new uint8_t[XMA_CONTEXT_DATA::kSamplesPerFrame * 2 * 2];
current_frame_pos_ = 0; current_frame_pos_ = 0;
frame_samples_size_ = 0; frame_samples_size_ = 0;
@ -117,7 +116,7 @@ void XmaContext::Work() {
auto context_ptr = memory()->TranslateVirtual(guest_ptr()); auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr); XMA_CONTEXT_DATA data(context_ptr);
Process(data); DecodePackets(data);
data.Store(context_ptr); data.Store(context_ptr);
} }
@ -130,7 +129,7 @@ void XmaContext::Enable() {
XELOGAPU("XmaContext: kicking context %d (%d/%d bytes)", id(), XELOGAPU("XmaContext: kicking context %d (%d/%d bytes)", id(),
(data.input_buffer_read_offset & ~0x7FF) / 8, (data.input_buffer_read_offset & ~0x7FF) / 8,
(data.input_buffer_0_packet_count + data.input_buffer_1_packet_count) (data.input_buffer_0_packet_count + data.input_buffer_1_packet_count)
* XMA_CONTEXT_DATA::kBytesPerPacket); * kBytesPerPacket);
// Reset valid flags so our audio decoder knows to process this one. // Reset valid flags so our audio decoder knows to process this one.
data.input_buffer_0_valid = data.input_buffer_0_ptr != 0; data.input_buffer_0_valid = data.input_buffer_0_ptr != 0;
@ -189,7 +188,18 @@ void XmaContext::Release() {
DiscardPacket(); DiscardPacket();
} }
void XmaContext::Process(XMA_CONTEXT_DATA& data) { int XmaContext::GetSampleRate(int id) {
switch (id) {
case 0: return 24000;
case 1: return 32000;
case 2: return 44100;
case 3: return 48000;
}
assert_always();
return 0;
}
void XmaContext::DecodePackets(XMA_CONTEXT_DATA& data) {
SCOPE_profile_cpu_f("apu"); SCOPE_profile_cpu_f("apu");
// What I see: // What I see:
@ -218,15 +228,13 @@ void XmaContext::Process(XMA_CONTEXT_DATA& data) {
return; return;
} }
// Translate this for future use.
uint8_t* output_buffer = memory()->TranslatePhysical(data.output_buffer_ptr);
// Output buffers are in raw PCM samples, 256 bytes per block. // Output buffers are in raw PCM samples, 256 bytes per block.
// Output buffer is a ring buffer. We need to write from the write offset // Output buffer is a ring buffer. We need to write from the write offset
// to the read offset. // to the read offset.
uint32_t output_capacity = data.output_buffer_block_count * 256; uint8_t* output_buffer = memory()->TranslatePhysical(data.output_buffer_ptr);
uint32_t output_read_offset = data.output_buffer_read_offset * 256; uint32_t output_capacity = data.output_buffer_block_count * kBytesPerSubframe;
uint32_t output_write_offset = data.output_buffer_write_offset * 256; uint32_t output_read_offset = data.output_buffer_read_offset * kBytesPerSubframe;
uint32_t output_write_offset = data.output_buffer_write_offset * kBytesPerSubframe;
RingBuffer output_rb(output_buffer, output_capacity); RingBuffer output_rb(output_buffer, output_capacity);
output_rb.set_read_offset(output_read_offset); output_rb.set_read_offset(output_read_offset);
@ -242,7 +250,7 @@ void XmaContext::Process(XMA_CONTEXT_DATA& data) {
int read_bytes = 0; int read_bytes = 0;
int decode_attempts_remaining = 3; int decode_attempts_remaining = 3;
uint8_t work_buffer[XMA_CONTEXT_DATA::kOutputMaxSizeBytes]; uint8_t work_buffer[kOutputMaxSizeBytes];
while (decode_attempts_remaining) { while (decode_attempts_remaining) {
read_bytes = DecodePacket(work_buffer, 0, output_remaining_bytes); read_bytes = DecodePacket(work_buffer, 0, output_remaining_bytes);
if (read_bytes >= 0) { if (read_bytes >= 0) {
@ -283,7 +291,7 @@ void XmaContext::Process(XMA_CONTEXT_DATA& data) {
continue; continue;
} else if (data.input_buffer_0_valid || data.input_buffer_1_valid) { } else if (data.input_buffer_0_valid || data.input_buffer_1_valid) {
// Done with previous packet, so grab a new one. // Done with previous packet, so grab a new one.
int ret = PreparePacket(data); int ret = StartPacket(data);
if (ret <= 0) { if (ret <= 0) {
// No more data (but may have prepared a packet) // No more data (but may have prepared a packet)
data.input_buffer_0_valid = 0; data.input_buffer_0_valid = 0;
@ -299,8 +307,7 @@ void XmaContext::Process(XMA_CONTEXT_DATA& data) {
data.output_buffer_valid = 0; data.output_buffer_valid = 0;
} }
int XmaContext::StartPacket(XMA_CONTEXT_DATA &data) {
int XmaContext::PreparePacket(XMA_CONTEXT_DATA &data) {
// Translate pointers for future use. // Translate pointers for future use.
uint8_t* in0 = data.input_buffer_0_valid uint8_t* in0 = data.input_buffer_0_valid
? memory()->TranslatePhysical(data.input_buffer_0_ptr) ? memory()->TranslatePhysical(data.input_buffer_0_ptr)
@ -309,16 +316,7 @@ int XmaContext::PreparePacket(XMA_CONTEXT_DATA &data) {
? memory()->TranslatePhysical(data.input_buffer_1_ptr) ? memory()->TranslatePhysical(data.input_buffer_1_ptr)
: nullptr; : nullptr;
int sample_rate = 0; int sample_rate = GetSampleRate(data.sample_rate);
if (data.sample_rate == 0) {
sample_rate = 24000;
} else if (data.sample_rate == 1) {
sample_rate = 32000;
} else if (data.sample_rate == 2) {
sample_rate = 44100;
} else if (data.sample_rate == 3) {
sample_rate = 48000;
}
int channels = data.is_stereo ? 2 : 1; int channels = data.is_stereo ? 2 : 1;
// See if we've finished with the input. // See if we've finished with the input.
@ -351,11 +349,11 @@ int XmaContext::PreparePacket(XMA_CONTEXT_DATA &data) {
auto packet = input_buffer + input_offset_bytes; auto packet = input_buffer + input_offset_bytes;
assert_true(input_offset_bytes % 2048 == 0); assert_true(input_offset_bytes % 2048 == 0);
PreparePacket(packet, seq_offset_bytes, PreparePacket(packet, seq_offset_bytes,
XMA_CONTEXT_DATA::kBytesPerPacket, kBytesPerPacket,
sample_rate, channels); sample_rate, channels);
data.input_buffer_read_offset += XMA_CONTEXT_DATA::kBytesPerPacket * 8; data.input_buffer_read_offset += kBytesPerPacket * 8;
input_remaining_bytes -= XMA_CONTEXT_DATA::kBytesPerPacket; input_remaining_bytes -= kBytesPerPacket;
if (input_remaining_bytes <= 0) { if (input_remaining_bytes <= 0) {
// Used the last of the data but prepared a packet // Used the last of the data but prepared a packet
return 0; return 0;
@ -370,7 +368,7 @@ int XmaContext::PreparePacket(XMA_CONTEXT_DATA &data) {
int XmaContext::PreparePacket(uint8_t *input, size_t seq_offset, size_t size, int XmaContext::PreparePacket(uint8_t *input, size_t seq_offset, size_t size,
int sample_rate, int channels) { int sample_rate, int channels) {
if (size != XMA_CONTEXT_DATA::kBytesPerPacket) { if (size != kBytesPerPacket) {
// Invalid packet size! // Invalid packet size!
assert_always(); assert_always();
return 1; return 1;
@ -387,16 +385,18 @@ int XmaContext::PreparePacket(uint8_t *input, size_t seq_offset, size_t size,
(*((int *)packet_data_) & 0xFFFEFF08); (*((int *)packet_data_) & 0xFFFEFF08);
packet_->data = packet_data_; packet_->data = packet_data_;
packet_->size = XMA_CONTEXT_DATA::kBytesPerPacket; packet_->size = kBytesPerPacket;
// Re-initialize the context with new sample rate and channels // Re-initialize the context with new sample rate and channels
if (context_->sample_rate != sample_rate || context_->channels != channels) { if (context_->sample_rate != sample_rate || context_->channels != channels) {
context_->sample_rate = sample_rate;
context_->channels = channels;
// We have to reopen the codec so it'll realloc whatever data it needs. // We have to reopen the codec so it'll realloc whatever data it needs.
// TODO: Find a better way. // TODO: Find a better way.
avcodec_close(context_); avcodec_close(context_);
context_->sample_rate = sample_rate;
context_->channels = channels;
extra_data_.channel_mask = channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO;
if (avcodec_open2(context_, codec_, NULL) < 0) { if (avcodec_open2(context_, codec_, NULL) < 0) {
XELOGE("XmaContext: Failed to reopen libav context"); XELOGE("XmaContext: Failed to reopen libav context");
return 1; return 1;
@ -449,7 +449,7 @@ int XmaContext::DecodePacket(uint8_t *output, size_t output_offset,
// Successfully decoded a frame // Successfully decoded a frame
if (got_frame) { if (got_frame) {
// Validity checks. // Validity checks.
if (decoded_frame_->nb_samples > XMA_CONTEXT_DATA::kSamplesPerFrame) { if (decoded_frame_->nb_samples > kSamplesPerFrame) {
return -2; return -2;
} else if (context_->sample_fmt != AV_SAMPLE_FMT_FLTP) { } else if (context_->sample_fmt != AV_SAMPLE_FMT_FLTP) {
return -3; return -3;

View File

@ -44,13 +44,6 @@ namespace apu {
// Appears to be dumped in order (for the most part) // Appears to be dumped in order (for the most part)
struct XMA_CONTEXT_DATA { struct XMA_CONTEXT_DATA {
static const uint32_t kBytesPerPacket = 2048;
static const uint32_t kSamplesPerFrame = 512;
static const uint32_t kSamplesPerSubframe = 128;
static const uint32_t kOutputBytesPerBlock = 256;
static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
// DWORD 0 // DWORD 0
uint32_t input_buffer_0_packet_count : 12; // XMASetInputBuffer0, number of uint32_t input_buffer_0_packet_count : 12; // XMASetInputBuffer0, number of
// 2KB packets. Max 4095 packets. // 2KB packets. Max 4095 packets.
@ -122,8 +115,29 @@ struct XMA_CONTEXT_DATA {
}; };
static_assert_size(XMA_CONTEXT_DATA, 64); static_assert_size(XMA_CONTEXT_DATA, 64);
#pragma pack(push, 1)
struct WmaProExtraData {
uint16_t bits_per_sample;
uint32_t channel_mask;
uint8_t unk06[8];
uint16_t decode_flags;
uint8_t unk10[2];
};
static_assert_size(WmaProExtraData, 18);
#pragma pack(pop)
class XmaContext { class XmaContext {
public: public:
static const uint32_t kBytesPerPacket = 2048;
static const uint32_t kBytesPerSample = 2;
static const uint32_t kSamplesPerFrame = 512;
static const uint32_t kSamplesPerSubframe = 128;
static const uint32_t kBytesPerSubframe = kSamplesPerSubframe * kBytesPerSample;
static const uint32_t kOutputBytesPerBlock = 256;
static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
XmaContext(); XmaContext();
~XmaContext(); ~XmaContext();
@ -147,8 +161,10 @@ class XmaContext {
void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
private: private:
void Process(XMA_CONTEXT_DATA& data); static int GetSampleRate(int id);
int PreparePacket(XMA_CONTEXT_DATA &data);
void DecodePackets(XMA_CONTEXT_DATA& data);
int StartPacket(XMA_CONTEXT_DATA &data);
int PreparePacket(uint8_t* input, size_t seq_offset, size_t size, int PreparePacket(uint8_t* input, size_t seq_offset, size_t size,
int sample_rate, int channels); int sample_rate, int channels);
@ -164,17 +180,20 @@ class XmaContext {
bool is_allocated_; bool is_allocated_;
bool is_enabled_; bool is_enabled_;
bool decoding_packet_;
// libav structures // libav structures
AVCodec* codec_; AVCodec* codec_;
AVCodecContext* context_; AVCodecContext* context_;
AVFrame* decoded_frame_; AVFrame* decoded_frame_;
AVPacket* packet_; AVPacket* packet_;
WmaProExtraData extra_data_;
size_t current_frame_pos_; size_t current_frame_pos_;
uint8_t* current_frame_; uint8_t* current_frame_;
uint32_t frame_samples_size_; uint32_t frame_samples_size_;
uint8_t packet_data_[XMA_CONTEXT_DATA::kBytesPerPacket]; uint8_t packet_data_[kBytesPerPacket];
}; };
} // namespace apu } // namespace apu

View File

@ -58,9 +58,6 @@ class XmaDecoder {
private: private:
void WorkerThreadMain(); void WorkerThreadMain();
void ProcessContext(XmaContext& context, XMA_CONTEXT_DATA& data);
int PreparePacket(XmaContext& context, XMA_CONTEXT_DATA& data);
static uint64_t MMIOReadRegisterThunk(void* ppc_context, XmaDecoder* as, static uint64_t MMIOReadRegisterThunk(void* ppc_context, XmaDecoder* as,
uint32_t addr) { uint32_t addr) {
return as->ReadRegister(addr); return as->ReadRegister(addr);