mirror of
https://github.com/sal063/AC6_recomp
synced 2026-05-24 07:11:16 -04:00
1450 lines
54 KiB
Diff
1450 lines
54 KiB
Diff
From a0271eca2604f6fc655eaa0b572e9a4fc3bd97f2 Mon Sep 17 00:00:00 2001
|
|
From: Tom <1568512+tomcl7@users.noreply.github.com>
|
|
Date: Fri, 20 Mar 2026 08:18:02 -0400
|
|
Subject: [PATCH] feat(audio): rewrite XMA decoder with loop fixes from Xenia
|
|
Canary
|
|
|
|
Ports xma_context_new from Xenia Canary, fixing audio looping and improving decoder correctness:
|
|
- Exact-match loop detection (== not >=) with subframe precision
|
|
- Decode/Consume split architecture for subframe-level output control
|
|
- StoreContextMerged to prevent race conditions with game context writes
|
|
- SwapInputBuffer sets read offset past packet header
|
|
- Output space uses subframe_decode_count instead of full frame
|
|
- Consume-only context support, packet skip 0xFF, split frame headers
|
|
- XMA_CONTEXT_DATA: identified error_status, output_buffer_padding fields
|
|
- XMA helpers: const correctness, IsPacketXma2Type
|
|
---
|
|
include/rex/audio/xma/context.h | 136 +++--
|
|
include/rex/audio/xma/helpers.h | 30 +-
|
|
src/audio/xma_context.cpp | 994 ++++++++++++++------------------
|
|
3 files changed, 530 insertions(+), 630 deletions(-)
|
|
|
|
diff --git a/include/native/audio/xma/context.h b/include/native/audio/xma/context.h
|
|
index 8dfd17c2..5298d1eb 100644
|
|
--- a/include/native/audio/xma/context.h
|
|
+++ b/include/native/audio/xma/context.h
|
|
@@ -14,8 +14,6 @@
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
-#include <queue>
|
|
-// #include <vector>
|
|
|
|
#include <rex/kernel.h>
|
|
#include <rex/memory.h>
|
|
@@ -69,7 +67,8 @@ struct XMA_CONTEXT_DATA {
|
|
uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be
|
|
// subframe_decode_count
|
|
uint32_t subframe_decode_count : 4; // +20bit
|
|
- uint32_t subframe_skip_count : 3; // +24bit
|
|
+ uint32_t output_buffer_padding : 3; // +24bit, extra output buffer blocks
|
|
+ // reserved per decoded frame
|
|
uint32_t sample_rate : 2; // +27bit enum of sample rates
|
|
uint32_t is_stereo : 1; // +29bit
|
|
uint32_t unk_dword_1_c : 1; // +30bit
|
|
@@ -77,12 +76,14 @@ struct XMA_CONTEXT_DATA {
|
|
|
|
// DWORD 2
|
|
uint32_t input_buffer_read_offset : 26; // XMAGetInputBufferReadOffset
|
|
- uint32_t unk_dword_2 : 6; // ErrorStatus/ErrorSet (?)
|
|
+ uint32_t error_status : 5; // ErrorStatus
|
|
+ uint32_t error_set : 1; // ErrorSet
|
|
|
|
// DWORD 3
|
|
- uint32_t loop_start : 26; // XMASetLoopData LoopStartOffset
|
|
- // frame offset in bits
|
|
- uint32_t unk_dword_3 : 6; // ? ParserErrorStatus/ParserErrorSet(?)
|
|
+ uint32_t loop_start : 26; // XMASetLoopData LoopStartOffset
|
|
+ // frame offset in bits
|
|
+ uint32_t parser_error_status : 5; // ParserErrorStatus
|
|
+ uint32_t parser_error_set : 1; // ParserErrorSet
|
|
|
|
// DWORD 4
|
|
uint32_t loop_end : 26; // XMASetLoopData LoopEndOffset
|
|
@@ -118,6 +119,32 @@ struct XMA_CONTEXT_DATA {
|
|
memory::copy_and_swap(reinterpret_cast<uint32_t*>(ptr), reinterpret_cast<const uint32_t*>(this),
|
|
sizeof(XMA_CONTEXT_DATA) / 4);
|
|
}
|
|
+
|
|
+ bool IsInputBufferValid(uint8_t buffer_index) const {
|
|
+ return buffer_index == 0 ? input_buffer_0_valid : input_buffer_1_valid;
|
|
+ }
|
|
+
|
|
+ bool IsCurrentInputBufferValid() const { return IsInputBufferValid(current_buffer); }
|
|
+
|
|
+ bool IsAnyInputBufferValid() const { return input_buffer_0_valid || input_buffer_1_valid; }
|
|
+
|
|
+ uint32_t GetInputBufferAddress(uint8_t buffer_index) const {
|
|
+ return buffer_index == 0 ? input_buffer_0_ptr : input_buffer_1_ptr;
|
|
+ }
|
|
+
|
|
+ uint32_t GetCurrentInputBufferAddress() const { return GetInputBufferAddress(current_buffer); }
|
|
+
|
|
+ uint32_t GetInputBufferPacketCount(uint8_t buffer_index) const {
|
|
+ return buffer_index == 0 ? input_buffer_0_packet_count : input_buffer_1_packet_count;
|
|
+ }
|
|
+
|
|
+ uint32_t GetCurrentInputBufferPacketCount() const {
|
|
+ return GetInputBufferPacketCount(current_buffer);
|
|
+ }
|
|
+
|
|
+ bool IsConsumeOnlyContext() const {
|
|
+ return (input_buffer_0_packet_count | input_buffer_1_packet_count) == 0;
|
|
+ }
|
|
};
|
|
static_assert_size(XMA_CONTEXT_DATA, 64);
|
|
|
|
@@ -129,11 +156,26 @@ struct Xma2ExtraData {
|
|
static_assert_size(Xma2ExtraData, 34);
|
|
#pragma pack(pop)
|
|
|
|
+struct kPacketInfo {
|
|
+ uint8_t frame_count_ = 0;
|
|
+ uint8_t current_frame_ = 0;
|
|
+ uint32_t current_frame_size_ = 0;
|
|
+
|
|
+ bool isLastFrameInPacket() const {
|
|
+ return frame_count_ == 0 || current_frame_ == frame_count_ - 1;
|
|
+ }
|
|
+};
|
|
+
|
|
+static constexpr int kIdToSampleRate[4] = {24000, 32000, 44100, 48000};
|
|
+
|
|
class XmaContext {
|
|
public:
|
|
static const uint32_t kBytesPerPacket = 2048;
|
|
static const uint32_t kBitsPerPacket = kBytesPerPacket * 8;
|
|
- static const uint32_t kBitsPerHeader = 33;
|
|
+ static const uint32_t kBitsPerPacketHeader = 32;
|
|
+ static const uint32_t kBitsPerFrameHeader = 15;
|
|
+ static const uint32_t kBytesPerPacketHeader = 4;
|
|
+ static const uint32_t kBytesPerPacketData = kBytesPerPacket - kBytesPerPacketHeader;
|
|
|
|
static const uint32_t kBytesPerSample = 2;
|
|
static const uint32_t kSamplesPerFrame = 512;
|
|
@@ -141,8 +183,9 @@ class XmaContext {
|
|
static const uint32_t kBytesPerFrameChannel = kSamplesPerFrame * kBytesPerSample;
|
|
static const uint32_t kBytesPerSubframeChannel = kSamplesPerSubframe * kBytesPerSample;
|
|
|
|
- // static const uint32_t kOutputBytesPerBlock = 256;
|
|
- // static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
|
|
+ static const uint32_t kOutputBytesPerBlock = 256;
|
|
+ static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
|
|
+ static const uint32_t kMaxFrameSizeinBits = 0x4000 - kBitsPerPacketHeader;
|
|
|
|
explicit XmaContext();
|
|
~XmaContext();
|
|
@@ -181,25 +224,32 @@ class XmaContext {
|
|
|
|
private:
|
|
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
|
|
- static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data, bool ignore_input_buffer_offset);
|
|
- static void NextPacket(XMA_CONTEXT_DATA* data);
|
|
static int GetSampleRate(int id);
|
|
- // Get the offset of the next frame. Does not traverse packets.
|
|
- static size_t GetNextFrame(uint8_t* block, size_t size, size_t bit_offset);
|
|
- // Get the containing packet number of the frame pointed to by the offset.
|
|
- static int GetFramePacketNumber(uint8_t* block, size_t size, size_t bit_offset);
|
|
- // Get the packet number and the index of the frame inside that packet
|
|
- static std::tuple<int, int> GetFrameNumber(uint8_t* block, size_t size, size_t bit_offset);
|
|
- // Get the number of frames contained in the packet (including truncated) and
|
|
- // if the last frame is split.
|
|
- static std::tuple<int, bool> GetPacketFrameCount(uint8_t* packet);
|
|
-
|
|
- // Convert sample format and swap bytes
|
|
- static void ConvertFrame(const uint8_t** samples, bool is_two_channel, uint8_t* output_buffer);
|
|
+ static int16_t GetPacketNumber(size_t size, size_t bit_offset);
|
|
+ static uint32_t GetCurrentInputBufferSize(XMA_CONTEXT_DATA* data);
|
|
+
|
|
+ kPacketInfo GetPacketInfo(uint8_t* packet, uint32_t frame_offset);
|
|
+ uint32_t GetAmountOfBitsToRead(uint32_t remaining_stream_bits, uint32_t frame_size);
|
|
+ const uint8_t* GetNextPacket(XMA_CONTEXT_DATA* data, uint32_t next_packet_index,
|
|
+ uint32_t current_input_packet_count);
|
|
+ uint32_t GetNextPacketReadOffset(uint8_t* buffer, uint32_t next_packet_index,
|
|
+ uint32_t current_input_packet_count);
|
|
+ uint8_t* GetCurrentInputBuffer(XMA_CONTEXT_DATA* data);
|
|
|
|
- bool ValidFrameOffset(uint8_t* block, size_t size_bytes, size_t frame_offset_bits);
|
|
void Decode(XMA_CONTEXT_DATA* data);
|
|
- int PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel);
|
|
+ void Consume(memory::RingBuffer* output_rb, const XMA_CONTEXT_DATA* data);
|
|
+ void UpdateLoopStatus(XMA_CONTEXT_DATA* data);
|
|
+ void ClearLocked(XMA_CONTEXT_DATA* data);
|
|
+
|
|
+ memory::RingBuffer PrepareOutputRingBuffer(XMA_CONTEXT_DATA* data);
|
|
+ int PrepareDecoder(int sample_rate, bool is_two_channel);
|
|
+ void PreparePacket(uint32_t frame_size, uint32_t frame_padding);
|
|
+ bool DecodePacket(AVCodecContext* av_context, const AVPacket* av_packet, AVFrame* av_frame);
|
|
+
|
|
+ void StoreContextMerged(const XMA_CONTEXT_DATA& data, const XMA_CONTEXT_DATA& initial_data,
|
|
+ uint8_t* context_ptr);
|
|
+
|
|
+ static void ConvertFrame(const uint8_t** samples, bool is_two_channel, uint8_t* output_buffer);
|
|
|
|
memory::Memory* memory_ = nullptr;
|
|
std::unique_ptr<rex::thread::Event> work_completion_event_;
|
|
@@ -209,35 +259,27 @@ class XmaContext {
|
|
std::mutex lock_;
|
|
std::atomic<bool> is_allocated_ = false;
|
|
std::atomic<bool> is_enabled_ = false;
|
|
- // bool is_dirty_ = true;
|
|
|
|
// ffmpeg structures
|
|
AVPacket* av_packet_ = nullptr;
|
|
AVCodec* av_codec_ = nullptr;
|
|
AVCodecContext* av_context_ = nullptr;
|
|
AVFrame* av_frame_ = nullptr;
|
|
- // uint32_t decoded_consumed_samples_ = 0; // TODO do this dynamically
|
|
- // int decoded_idx_ = -1;
|
|
-
|
|
- // bool partial_frame_saved_ = false;
|
|
- // bool partial_frame_size_known_ = false;
|
|
- // size_t partial_frame_total_size_bits_ = 0;
|
|
- // size_t partial_frame_start_offset_bits_ = 0;
|
|
- // size_t partial_frame_offset_bits_ = 0; // blah internal don't use this
|
|
- // std::vector<uint8_t> partial_frame_buffer_;
|
|
- uint32_t packets_skip_ = 0;
|
|
-
|
|
- // bool split_frame_pending_ = false;
|
|
- uint32_t split_frame_len_ = 0;
|
|
- uint32_t split_frame_len_partial_ = 0;
|
|
- uint8_t split_frame_padding_start_ = 0;
|
|
- // first byte contains bit offset information
|
|
- std::array<uint8_t, 1 + 4096> xma_frame_;
|
|
|
|
- // uint8_t* current_frame_ = nullptr;
|
|
- // conversion buffer for 2 channel frame
|
|
+ // Packet data buffer (two packets worth for split frame handling)
|
|
+ std::array<uint8_t, kBytesPerPacketData * 2> input_buffer_;
|
|
+ // First byte contains bit offset information
|
|
+ std::array<uint8_t, 1 + 4096> xma_frame_;
|
|
+ // Conversion buffer for up to 2-channel frame
|
|
std::array<uint8_t, kBytesPerFrameChannel * 2> raw_frame_;
|
|
- // std::vector<uint8_t> current_frame_ = std::vector<uint8_t>(0);
|
|
+
|
|
+ // Output buffer tracking
|
|
+ int32_t remaining_subframe_blocks_in_output_buffer_ = 0;
|
|
+ uint8_t current_frame_remaining_subframes_ = 0;
|
|
+
|
|
+ // Loop subframe precision state
|
|
+ uint8_t loop_frame_output_limit_ = 0;
|
|
+ bool loop_start_skip_pending_ = false;
|
|
};
|
|
|
|
} // namespace rex::audio
|
|
diff --git a/include/native/audio/xma/helpers.h b/include/native/audio/xma/helpers.h
|
|
index b2e0e10e..638e406a 100644
|
|
--- a/include/native/audio/xma/helpers.h
|
|
+++ b/include/native/audio/xma/helpers.h
|
|
@@ -17,30 +17,30 @@
|
|
|
|
namespace rex::audio::xma {
|
|
|
|
-static const uint32_t kMaxFrameLength = 0x7FFF;
|
|
+static constexpr uint32_t kMaxFrameLength = 0x7FFF;
|
|
|
|
-// Get number of frames that /begin/ in this packet.
|
|
-inline uint32_t GetPacketFrameCount(uint8_t* packet) {
|
|
- return (uint8_t)(packet[0] >> 2);
|
|
+// Get number of frames that /begin/ in this packet. Valid only for XMA2 packets.
|
|
+inline uint8_t GetPacketFrameCount(const uint8_t* packet) {
|
|
+ return packet[0] >> 2;
|
|
}
|
|
|
|
// Get the first frame offset in bits
|
|
-inline uint32_t GetPacketFrameOffset(uint8_t* packet) {
|
|
- uint32_t val = (uint16_t)(((packet[0] & 0x3) << 13) | (packet[1] << 5) | (packet[2] >> 3));
|
|
- // if (val > kBitsPerPacket - kBitsPerHeader) {
|
|
- // // There is no data in this packet
|
|
- // return -1;
|
|
- // } else {
|
|
+inline uint32_t GetPacketFrameOffset(const uint8_t* packet) {
|
|
+ uint32_t val =
|
|
+ static_cast<uint16_t>(((packet[0] & 0x3) << 13) | (packet[1] << 5) | (packet[2] >> 3));
|
|
return val + 32;
|
|
- // }
|
|
}
|
|
|
|
-inline uint32_t GetPacketMetadata(uint8_t* packet) {
|
|
- return (uint8_t)(packet[2] & 0x7);
|
|
+inline uint8_t GetPacketMetadata(const uint8_t* packet) {
|
|
+ return packet[2] & 0x7;
|
|
}
|
|
|
|
-inline uint32_t GetPacketSkipCount(uint8_t* packet) {
|
|
- return (uint8_t)(packet[3]);
|
|
+inline bool IsPacketXma2Type(const uint8_t* packet) {
|
|
+ return GetPacketMetadata(packet) == 1;
|
|
+}
|
|
+
|
|
+inline uint8_t GetPacketSkipCount(const uint8_t* packet) {
|
|
+ return packet[3];
|
|
}
|
|
|
|
} // namespace rex::audio::xma
|
|
diff --git a/src/native/audio/xma/context.cpp b/src/native/audio/xma/context.cpp
|
|
index 49aae593..a6e79733 100644
|
|
--- a/src/native/audio/xma/context.cpp
|
|
+++ b/src/native/audio/xma/context.cpp
|
|
@@ -52,9 +52,6 @@ XmaContext::~XmaContext() {
|
|
if (av_frame_) {
|
|
av_frame_free(&av_frame_);
|
|
}
|
|
- // if (current_frame_) {
|
|
- // delete[] current_frame_;
|
|
- // }
|
|
}
|
|
|
|
int XmaContext::Setup(uint32_t id, memory::Memory* memory, uint32_t guest_ptr) {
|
|
@@ -94,35 +91,66 @@ int XmaContext::Setup(uint32_t id, memory::Memory* memory, uint32_t guest_ptr) {
|
|
}
|
|
|
|
bool XmaContext::Work() {
|
|
- std::lock_guard<std::mutex> lock(lock_);
|
|
if (!is_allocated() || !is_enabled()) {
|
|
return false;
|
|
}
|
|
|
|
+ std::lock_guard<std::mutex> lock(lock_);
|
|
set_is_enabled(false);
|
|
|
|
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
|
|
XMA_CONTEXT_DATA data(context_ptr);
|
|
- Decode(&data);
|
|
- data.Store(context_ptr);
|
|
- return true;
|
|
-}
|
|
+ const XMA_CONTEXT_DATA initial_data = data;
|
|
|
|
-void XmaContext::Enable() {
|
|
- std::lock_guard<std::mutex> lock(lock_);
|
|
+ if (!data.output_buffer_valid) {
|
|
+ return true;
|
|
+ }
|
|
|
|
- auto context_ptr = memory()->TranslateVirtual(guest_ptr());
|
|
- XMA_CONTEXT_DATA data(context_ptr);
|
|
+ memory::RingBuffer output_rb = PrepareOutputRingBuffer(&data);
|
|
|
|
- REXAPU_TRACE("XmaContext: kicking context {} (buffer {} {}/{} bits)", id(),
|
|
- static_cast<uint32_t>(data.current_buffer),
|
|
- static_cast<uint32_t>(data.input_buffer_read_offset),
|
|
- (data.current_buffer == 0 ? data.input_buffer_0_packet_count
|
|
- : data.input_buffer_1_packet_count) *
|
|
- kBitsPerPacket);
|
|
+ // Consume-only context: no input, just drain remaining subframes.
|
|
+ if (data.IsConsumeOnlyContext()) {
|
|
+ if (current_frame_remaining_subframes_ == 0) {
|
|
+ return true;
|
|
+ }
|
|
+ Consume(&output_rb, &data);
|
|
+ data.output_buffer_write_offset = output_rb.write_offset() / kOutputBytesPerBlock;
|
|
+ StoreContextMerged(data, initial_data, context_ptr);
|
|
+ return true;
|
|
+ }
|
|
|
|
- data.Store(context_ptr);
|
|
+ // Minimum free blocks needed before attempting a decode.
|
|
+ // Use subframe_decode_count (clamped to 1) instead of full frame size.
|
|
+ const uint32_t effective_sdc = std::max(static_cast<uint32_t>(1), data.subframe_decode_count);
|
|
+ const int32_t minimum_subframe_decode_count =
|
|
+ static_cast<int32_t>(effective_sdc) + data.output_buffer_padding;
|
|
+
|
|
+ if (minimum_subframe_decode_count > remaining_subframe_blocks_in_output_buffer_) {
|
|
+ StoreContextMerged(data, initial_data, context_ptr);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ while (remaining_subframe_blocks_in_output_buffer_ >= minimum_subframe_decode_count) {
|
|
+ Decode(&data);
|
|
+ Consume(&output_rb, &data);
|
|
+
|
|
+ if (!data.IsAnyInputBufferValid() || data.error_status == 4) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
|
|
+ data.output_buffer_write_offset = output_rb.write_offset() / kOutputBytesPerBlock;
|
|
+
|
|
+ if (output_rb.empty()) {
|
|
+ data.output_buffer_valid = 0;
|
|
+ }
|
|
+
|
|
+ StoreContextMerged(data, initial_data, context_ptr);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+void XmaContext::Enable() {
|
|
+ std::lock_guard<std::mutex> lock(lock_);
|
|
set_is_enabled(true);
|
|
}
|
|
|
|
@@ -143,685 +171,515 @@ void XmaContext::Clear() {
|
|
|
|
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
|
|
XMA_CONTEXT_DATA data(context_ptr);
|
|
+ ClearLocked(&data);
|
|
+ data.Store(context_ptr);
|
|
+}
|
|
|
|
- data.input_buffer_0_valid = 0;
|
|
- data.input_buffer_1_valid = 0;
|
|
- data.output_buffer_valid = 0;
|
|
+void XmaContext::ClearLocked(XMA_CONTEXT_DATA* data) {
|
|
+ data->input_buffer_0_valid = 0;
|
|
+ data->input_buffer_1_valid = 0;
|
|
+ data->output_buffer_valid = 0;
|
|
|
|
- data.output_buffer_read_offset = 0;
|
|
- data.output_buffer_write_offset = 0;
|
|
+ data->input_buffer_read_offset = kBitsPerPacketHeader;
|
|
+ data->output_buffer_read_offset = 0;
|
|
+ data->output_buffer_write_offset = 0;
|
|
|
|
- data.Store(context_ptr);
|
|
+ current_frame_remaining_subframes_ = 0;
|
|
+ loop_frame_output_limit_ = 0;
|
|
+ loop_start_skip_pending_ = false;
|
|
}
|
|
|
|
void XmaContext::Disable() {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
- REXAPU_TRACE("XmaContext: disabling context {}", id());
|
|
set_is_enabled(false);
|
|
}
|
|
|
|
void XmaContext::Release() {
|
|
- // Lock it in case the decoder thread is working on it now.
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
assert_true(is_allocated());
|
|
|
|
set_is_allocated(false);
|
|
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
|
|
- std::memset(context_ptr, 0, sizeof(XMA_CONTEXT_DATA)); // Zero it.
|
|
+ std::memset(context_ptr, 0, sizeof(XMA_CONTEXT_DATA));
|
|
}
|
|
|
|
void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
|
|
- // No more frames.
|
|
if (data->current_buffer == 0) {
|
|
data->input_buffer_0_valid = 0;
|
|
} else {
|
|
data->input_buffer_1_valid = 0;
|
|
}
|
|
data->current_buffer ^= 1;
|
|
- data->input_buffer_read_offset = 0;
|
|
+ data->input_buffer_read_offset = kBitsPerPacketHeader;
|
|
}
|
|
|
|
-bool XmaContext::TrySetupNextLoop(XMA_CONTEXT_DATA* data, bool ignore_input_buffer_offset) {
|
|
- // Setup the input buffer offset if next loop exists.
|
|
- // TODO(Pseudo-Kernel): Need to handle loop in the following cases.
|
|
- // 1. loop_start == loop_end == 0
|
|
- // 2. loop_start > loop_end && loop_count > 0
|
|
- if (data->loop_count > 0 && data->loop_start < data->loop_end &&
|
|
- (ignore_input_buffer_offset || data->input_buffer_read_offset >= data->loop_end)) {
|
|
- // Loop back to the beginning.
|
|
- data->input_buffer_read_offset = data->loop_start;
|
|
- if (data->loop_count < 255) {
|
|
- data->loop_count--;
|
|
- }
|
|
- return true;
|
|
+void XmaContext::UpdateLoopStatus(XMA_CONTEXT_DATA* data) {
|
|
+ if (data->loop_count == 0) {
|
|
+ return;
|
|
}
|
|
- return false;
|
|
-}
|
|
|
|
-/*
|
|
-void XmaContext::NextPacket(
|
|
- uint8_t* input_buffer,
|
|
- uint32_t input_size,
|
|
- uint32_t input_buffer_read_offset) {
|
|
-*/
|
|
-void XmaContext::NextPacket(XMA_CONTEXT_DATA* data) {
|
|
- // auto packet_idx = GetFramePacketNumber(input_buffer, input_size,
|
|
- // input_buffer_read_offset);
|
|
+ const uint32_t loop_start = std::max(kBitsPerPacketHeader, data->loop_start);
|
|
+ const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
|
|
+
|
|
+ if (data->input_buffer_read_offset != loop_end) {
|
|
+ return;
|
|
+ }
|
|
|
|
- // packet_idx++;
|
|
- // if (packet_idx++ >= input_size)
|
|
+ data->input_buffer_read_offset = loop_start;
|
|
+ loop_start_skip_pending_ = true;
|
|
+
|
|
+ if (data->loop_count < 255) {
|
|
+ data->loop_count--;
|
|
+ }
|
|
}
|
|
|
|
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;
|
|
+ return kIdToSampleRate[std::min(id, 3)];
|
|
}
|
|
|
|
-bool XmaContext::ValidFrameOffset(uint8_t* block, size_t size_bytes, size_t frame_offset_bits) {
|
|
- uint32_t packet_num = GetFramePacketNumber(block, size_bytes, frame_offset_bits);
|
|
- if (packet_num == -1) {
|
|
- // Invalid packet number
|
|
- return false;
|
|
+int16_t XmaContext::GetPacketNumber(size_t size, size_t bit_offset) {
|
|
+ if (bit_offset < kBitsPerPacketHeader) {
|
|
+ assert_always();
|
|
+ return -1;
|
|
+ }
|
|
+ if (bit_offset >= (size << 3)) {
|
|
+ assert_always();
|
|
+ return -1;
|
|
}
|
|
+ size_t byte_offset = bit_offset >> 3;
|
|
+ size_t packet_number = byte_offset / kBytesPerPacket;
|
|
+ return static_cast<int16_t>(packet_number);
|
|
+}
|
|
|
|
- uint8_t* packet = block + (packet_num * kBytesPerPacket);
|
|
- size_t relative_offset_bits = frame_offset_bits % kBitsPerPacket;
|
|
+uint32_t XmaContext::GetCurrentInputBufferSize(XMA_CONTEXT_DATA* data) {
|
|
+ return data->GetCurrentInputBufferPacketCount() * kBytesPerPacket;
|
|
+}
|
|
|
|
- uint32_t first_frame_offset = xma::GetPacketFrameOffset(packet);
|
|
- if (first_frame_offset == -1 || first_frame_offset > kBitsPerPacket) {
|
|
- // Packet only contains a partial frame, so no frames can start here.
|
|
- return false;
|
|
+uint8_t* XmaContext::GetCurrentInputBuffer(XMA_CONTEXT_DATA* data) {
|
|
+ return memory()->TranslatePhysical(data->GetCurrentInputBufferAddress());
|
|
+}
|
|
+
|
|
+uint32_t XmaContext::GetAmountOfBitsToRead(uint32_t remaining_stream_bits, uint32_t frame_size) {
|
|
+ return std::min(remaining_stream_bits, frame_size);
|
|
+}
|
|
+
|
|
+const uint8_t* XmaContext::GetNextPacket(XMA_CONTEXT_DATA* data, uint32_t next_packet_index,
|
|
+ uint32_t current_input_packet_count) {
|
|
+ if (next_packet_index < current_input_packet_count) {
|
|
+ return memory()->TranslatePhysical(data->GetCurrentInputBufferAddress()) +
|
|
+ next_packet_index * kBytesPerPacket;
|
|
+ }
|
|
+
|
|
+ const uint8_t next_buffer_index = data->current_buffer ^ 1;
|
|
+ if (!data->IsInputBufferValid(next_buffer_index)) {
|
|
+ return nullptr;
|
|
+ }
|
|
+
|
|
+ const uint32_t next_buffer_address = data->GetInputBufferAddress(next_buffer_index);
|
|
+ if (!next_buffer_address) {
|
|
+ REXAPU_ERROR("XmaContext {}: Buffer marked valid but has null pointer!", id());
|
|
+ return nullptr;
|
|
}
|
|
|
|
+ return memory()->TranslatePhysical(next_buffer_address);
|
|
+}
|
|
+
|
|
+uint32_t XmaContext::GetNextPacketReadOffset(uint8_t* buffer, uint32_t next_packet_index,
|
|
+ uint32_t current_input_packet_count) {
|
|
+ while (next_packet_index < current_input_packet_count) {
|
|
+ uint8_t* next_packet = buffer + (next_packet_index * kBytesPerPacket);
|
|
+ const uint32_t packet_frame_offset = xma::GetPacketFrameOffset(next_packet);
|
|
+
|
|
+ if (packet_frame_offset <= kMaxFrameSizeinBits) {
|
|
+ return (next_packet_index * kBitsPerPacket) + packet_frame_offset;
|
|
+ }
|
|
+ next_packet_index++;
|
|
+ }
|
|
+
|
|
+ return kBitsPerPacketHeader;
|
|
+}
|
|
+
|
|
+memory::RingBuffer XmaContext::PrepareOutputRingBuffer(XMA_CONTEXT_DATA* data) {
|
|
+ const uint32_t output_capacity = data->output_buffer_block_count * kOutputBytesPerBlock;
|
|
+ const uint32_t output_read_offset = data->output_buffer_read_offset * kOutputBytesPerBlock;
|
|
+ const uint32_t output_write_offset = data->output_buffer_write_offset * kOutputBytesPerBlock;
|
|
+
|
|
+ if (output_capacity > kOutputMaxSizeBytes) {
|
|
+ REXAPU_WARN(
|
|
+ "XmaContext {}: Output buffer exceeds expected size! "
|
|
+ "(Actual: {} Max: {})",
|
|
+ id(), output_capacity, kOutputMaxSizeBytes);
|
|
+ }
|
|
+
|
|
+ uint8_t* output_buffer = memory()->TranslatePhysical(data->output_buffer_ptr);
|
|
+
|
|
+ memory::RingBuffer output_rb(output_buffer, output_capacity);
|
|
+ output_rb.set_read_offset(output_read_offset);
|
|
+ output_rb.set_write_offset(output_write_offset);
|
|
+ remaining_subframe_blocks_in_output_buffer_ =
|
|
+ static_cast<int32_t>(output_rb.write_count()) / kOutputBytesPerBlock;
|
|
+
|
|
+ return output_rb;
|
|
+}
|
|
+
|
|
+kPacketInfo XmaContext::GetPacketInfo(uint8_t* packet, uint32_t frame_offset) {
|
|
+ kPacketInfo packet_info = {};
|
|
+
|
|
+ const uint32_t first_frame_offset = xma::GetPacketFrameOffset(packet);
|
|
BitStream stream(packet, kBitsPerPacket);
|
|
stream.SetOffset(first_frame_offset);
|
|
+
|
|
+ if (frame_offset < first_frame_offset) {
|
|
+ packet_info.current_frame_ = 0;
|
|
+ packet_info.current_frame_size_ = first_frame_offset - frame_offset;
|
|
+ }
|
|
+
|
|
while (true) {
|
|
- if (stream.offset_bits() == relative_offset_bits) {
|
|
- return true;
|
|
+ if (stream.BitsRemaining() < kBitsPerFrameHeader) {
|
|
+ break;
|
|
}
|
|
|
|
- if (stream.BitsRemaining() < 15) {
|
|
- // Not enough room for another frame header.
|
|
- return false;
|
|
+ const uint64_t frame_size = stream.Peek(kBitsPerFrameHeader);
|
|
+ if (frame_size == 0 || frame_size == xma::kMaxFrameLength) {
|
|
+ break;
|
|
}
|
|
|
|
- uint64_t size = stream.Read(15);
|
|
- if ((size - 15) > stream.BitsRemaining()) {
|
|
- // Last frame.
|
|
- return false;
|
|
- } else if (size == 0x7FFF) {
|
|
- // Invalid frame (and last of this packet)
|
|
- return false;
|
|
+ if (stream.offset_bits() == frame_offset) {
|
|
+ packet_info.current_frame_ = packet_info.frame_count_;
|
|
+ packet_info.current_frame_size_ = static_cast<uint32_t>(frame_size);
|
|
}
|
|
|
|
- stream.Advance(size - 16);
|
|
+ packet_info.frame_count_++;
|
|
|
|
- // Read the trailing bit to see if frames follow
|
|
- if (stream.Read(1) == 0) {
|
|
+ if (frame_size > stream.BitsRemaining()) {
|
|
break;
|
|
}
|
|
- }
|
|
|
|
- return false;
|
|
-}
|
|
+ stream.Advance(frame_size - 1);
|
|
|
|
-static void dump_raw(AVFrame* frame, int id) {
|
|
- FILE* outfile = fopen(fmt::format("out{}.raw", id).c_str(), "ab");
|
|
- if (!outfile) {
|
|
- return;
|
|
+ if (stream.Read(1) == 0) {
|
|
+ break;
|
|
+ }
|
|
}
|
|
- size_t data_size = sizeof(float);
|
|
- for (int i = 0; i < frame->nb_samples; i++) {
|
|
- for (int ch = 0; ch < frame->channels; ch++) {
|
|
- fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
|
|
+
|
|
+ if (xma::IsPacketXma2Type(packet)) {
|
|
+ const uint8_t xma2_frame_count = xma::GetPacketFrameCount(packet);
|
|
+ if (xma2_frame_count > packet_info.frame_count_) {
|
|
+ if (packet_info.current_frame_size_ == 0) {
|
|
+ packet_info.current_frame_ = packet_info.frame_count_;
|
|
+ }
|
|
+ packet_info.frame_count_ = xma2_frame_count;
|
|
}
|
|
}
|
|
- fclose(outfile);
|
|
+ return packet_info;
|
|
}
|
|
|
|
-void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|
- SCOPE_profile_cpu_f("apu");
|
|
-
|
|
- // What I see:
|
|
- // XMA outputs 2 bytes per sample
|
|
- // 512 samples per frame (128 per subframe)
|
|
- // Max output size is data.output_buffer_block_count * 256
|
|
+void XmaContext::StoreContextMerged(const XMA_CONTEXT_DATA& data,
|
|
+ const XMA_CONTEXT_DATA& initial_data, uint8_t* context_ptr) {
|
|
+ XMA_CONTEXT_DATA fresh(context_ptr);
|
|
|
|
- // This decoder is fed packets (max 4095 per buffer)
|
|
- // Packets contain "some" frames
|
|
- // 32bit header (big endian)
|
|
+ fresh.loop_count = data.loop_count;
|
|
+ fresh.output_buffer_write_offset = data.output_buffer_write_offset;
|
|
+ if (initial_data.input_buffer_0_valid && !data.input_buffer_0_valid) {
|
|
+ fresh.input_buffer_0_valid = 0;
|
|
+ }
|
|
+ if (initial_data.input_buffer_1_valid && !data.input_buffer_1_valid) {
|
|
+ fresh.input_buffer_1_valid = 0;
|
|
+ }
|
|
|
|
- // Frames are the smallest thing the SPUs can decode.
|
|
- // They can and usually will span packets.
|
|
+ if (initial_data.output_buffer_valid && !data.output_buffer_valid) {
|
|
+ fresh.output_buffer_valid = 0;
|
|
+ }
|
|
|
|
- // Sample rates (data.sample_rate):
|
|
- // 0 - 24 kHz
|
|
- // 1 - 32 kHz
|
|
- // 2 - 44.1 kHz
|
|
- // 3 - 48 kHz
|
|
+ fresh.input_buffer_read_offset = data.input_buffer_read_offset;
|
|
+ fresh.error_status = data.error_status;
|
|
+ fresh.current_buffer = data.current_buffer;
|
|
+ fresh.output_buffer_read_offset = data.output_buffer_read_offset;
|
|
|
|
- // SPUs also support stereo decoding. (data.is_stereo)
|
|
+ fresh.Store(context_ptr);
|
|
+}
|
|
|
|
- // Check the output buffer - we cannot decode anything else if it's
|
|
- // unavailable.
|
|
- if (!data->output_buffer_valid) {
|
|
+void XmaContext::Consume(memory::RingBuffer* output_rb, const XMA_CONTEXT_DATA* data) {
|
|
+ if (!current_frame_remaining_subframes_) {
|
|
return;
|
|
}
|
|
|
|
- // No available data.
|
|
- if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
|
- data->output_buffer_valid = 0;
|
|
- return;
|
|
+ if (loop_frame_output_limit_ > 0) {
|
|
+ const uint8_t total_subframes = (kBytesPerFrameChannel / kOutputBytesPerBlock)
|
|
+ << data->is_stereo;
|
|
+ const uint8_t consumed = total_subframes - current_frame_remaining_subframes_;
|
|
+ if (consumed >= loop_frame_output_limit_) {
|
|
+ remaining_subframe_blocks_in_output_buffer_ -= data->output_buffer_padding;
|
|
+ current_frame_remaining_subframes_ = 0;
|
|
+ loop_frame_output_limit_ = 0;
|
|
+ return;
|
|
+ }
|
|
}
|
|
|
|
- // XAudio Loops
|
|
- // loop_count:
|
|
- // - XAUDIO2_MAX_LOOP_COUNT = 254
|
|
- // - XAUDIO2_LOOP_INFINITE = 255
|
|
- // loop_start/loop_end are bit offsets to a specific frame
|
|
-
|
|
- // Translate pointers for future use.
|
|
- // Sometimes the game will use rolling input buffers. If they do, we cannot
|
|
- // assume they form a complete block! In addition, the buffers DO NOT have
|
|
- // to be contiguous!
|
|
- uint8_t* in0 =
|
|
- data->input_buffer_0_valid ? memory()->TranslatePhysical(data->input_buffer_0_ptr) : nullptr;
|
|
- uint8_t* in1 =
|
|
- data->input_buffer_1_valid ? memory()->TranslatePhysical(data->input_buffer_1_ptr) : nullptr;
|
|
- uint8_t* current_input_buffer = data->current_buffer ? in1 : in0;
|
|
-
|
|
- REXAPU_TRACE("Processing context {} (offset {}, buffer {}, ptr {:p})", id(),
|
|
- static_cast<uint32_t>(data->input_buffer_read_offset),
|
|
- static_cast<uint32_t>(data->current_buffer),
|
|
- static_cast<void*>(current_input_buffer));
|
|
-
|
|
- size_t input_buffer_0_size = data->input_buffer_0_packet_count * kBytesPerPacket;
|
|
- size_t input_buffer_1_size = data->input_buffer_1_packet_count * kBytesPerPacket;
|
|
- size_t input_total_size = input_buffer_0_size + input_buffer_1_size;
|
|
-
|
|
- size_t current_input_size = data->current_buffer ? input_buffer_1_size : input_buffer_0_size;
|
|
- size_t current_input_packet_count = current_input_size / kBytesPerPacket;
|
|
-
|
|
- // 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
|
|
- // to the read offset.
|
|
- uint8_t* output_buffer = memory()->TranslatePhysical(data->output_buffer_ptr);
|
|
- uint32_t output_capacity = data->output_buffer_block_count * kBytesPerSubframeChannel;
|
|
- uint32_t output_read_offset = data->output_buffer_read_offset * kBytesPerSubframeChannel;
|
|
- uint32_t output_write_offset = data->output_buffer_write_offset * kBytesPerSubframeChannel;
|
|
-
|
|
- memory::RingBuffer output_rb(output_buffer, output_capacity);
|
|
- output_rb.set_read_offset(output_read_offset);
|
|
- output_rb.set_write_offset(output_write_offset);
|
|
+ const uint8_t effective_sdc = std::max(static_cast<uint32_t>(1), data->subframe_decode_count);
|
|
+ int8_t subframes_to_write = std::min(static_cast<int8_t>(current_frame_remaining_subframes_),
|
|
+ static_cast<int8_t>(effective_sdc));
|
|
|
|
- // We can only decode an entire frame and write it out at a time, so
|
|
- // don't save any samples.
|
|
- // TODO(JoelLinn): subframes when looping
|
|
- size_t output_remaining_bytes = output_rb.write_count();
|
|
- output_remaining_bytes -= output_remaining_bytes % (kBytesPerFrameChannel << data->is_stereo);
|
|
-
|
|
- // is_dirty_ = true; // TODO
|
|
- // is_dirty_ = false; // TODO
|
|
- assert_false(data->stop_when_done);
|
|
- assert_false(data->interrupt_when_done);
|
|
- static int total_samples = 0;
|
|
- bool reuse_input_buffer = false;
|
|
- // Decode until we can't write any more data.
|
|
- while (output_remaining_bytes > 0) {
|
|
- if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
|
- // Out of data.
|
|
- break;
|
|
+ if (loop_frame_output_limit_ > 0) {
|
|
+ const uint8_t total_subframes = (kBytesPerFrameChannel / kOutputBytesPerBlock)
|
|
+ << data->is_stereo;
|
|
+ const uint8_t consumed = total_subframes - current_frame_remaining_subframes_;
|
|
+ const int8_t remaining_until_limit = static_cast<int8_t>(loop_frame_output_limit_ - consumed);
|
|
+ if (subframes_to_write > remaining_until_limit) {
|
|
+ subframes_to_write = remaining_until_limit;
|
|
}
|
|
+ }
|
|
|
|
- // Setup the input buffer if we are at loop_end.
|
|
- // The input buffer must not be swapped out until all loops are processed.
|
|
- reuse_input_buffer = TrySetupNextLoop(data, false);
|
|
-
|
|
- // assert_true(packets_skip_ == 0);
|
|
- // assert_true(split_frame_len_ == 0);
|
|
- // assert_true(split_frame_len_partial_ == 0);
|
|
-
|
|
- // Where are we in the buffer (in XMA jargon)
|
|
- int packet_idx, frame_idx, frame_count;
|
|
- uint8_t* packet;
|
|
- bool frame_last_split;
|
|
-
|
|
- BitStream stream(current_input_buffer, current_input_size * 8);
|
|
- stream.SetOffset(data->input_buffer_read_offset);
|
|
-
|
|
- // if we had a buffer swap try to skip packets first
|
|
- if (packets_skip_ > 0) {
|
|
- packet_idx = GetFramePacketNumber(current_input_buffer, current_input_size,
|
|
- data->input_buffer_read_offset);
|
|
- while (packets_skip_ > 0) {
|
|
- packets_skip_--;
|
|
- packet_idx++;
|
|
- if (packet_idx >= current_input_packet_count) {
|
|
- if (!reuse_input_buffer) {
|
|
- // Last packet. Try setup once more.
|
|
- reuse_input_buffer = TrySetupNextLoop(data, true);
|
|
- }
|
|
- if (!reuse_input_buffer) {
|
|
- SwapInputBuffer(data);
|
|
- }
|
|
- return;
|
|
- }
|
|
- }
|
|
- // invalid frame pointer but needed for us
|
|
- data->input_buffer_read_offset = packet_idx * kBitsPerPacket;
|
|
- // continue;
|
|
- }
|
|
+ const int8_t raw_frame_read_offset =
|
|
+ ((kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo) -
|
|
+ current_frame_remaining_subframes_;
|
|
|
|
- if (split_frame_len_) {
|
|
- // handle a frame that was split over two packages
|
|
- packet_idx = GetFramePacketNumber(current_input_buffer, current_input_size,
|
|
- data->input_buffer_read_offset);
|
|
- packet = current_input_buffer + packet_idx * kBytesPerPacket;
|
|
- std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet);
|
|
- frame_idx = -1;
|
|
-
|
|
- stream = BitStream(current_input_buffer, (packet_idx + 1) * kBitsPerPacket);
|
|
- stream.SetOffset(packet_idx * kBitsPerPacket + 32);
|
|
-
|
|
- if (split_frame_len_ > xma::kMaxFrameLength) {
|
|
- // TODO write CopyPeekMethod
|
|
- auto offset = stream.offset_bits();
|
|
- stream.Copy(
|
|
- xma_frame_.data() + 1 + ((split_frame_len_partial_ + split_frame_padding_start_) / 8),
|
|
- 15 - split_frame_len_partial_);
|
|
- stream.SetOffset(offset);
|
|
- BitStream slen(xma_frame_.data() + 1, 15 + split_frame_padding_start_);
|
|
- slen.Advance(split_frame_padding_start_);
|
|
- split_frame_len_ = static_cast<int>(slen.Read(15));
|
|
- }
|
|
+ output_rb->Write(raw_frame_.data() + (kOutputBytesPerBlock * raw_frame_read_offset),
|
|
+ subframes_to_write * kOutputBytesPerBlock);
|
|
|
|
- if (frame_count > 0) {
|
|
- assert_true(xma::GetPacketFrameOffset(packet) - 32 ==
|
|
- split_frame_len_ - split_frame_len_partial_);
|
|
- }
|
|
-
|
|
- auto offset = stream.Copy(
|
|
- xma_frame_.data() + 1 + ((split_frame_len_partial_ + split_frame_padding_start_) / 8),
|
|
- split_frame_len_ - split_frame_len_partial_);
|
|
- assert_true(offset == (split_frame_padding_start_ + split_frame_len_partial_) % 8);
|
|
- } else {
|
|
- if (data->input_buffer_read_offset % kBitsPerPacket == 0) {
|
|
- // Invalid offset. Go ahead and set it.
|
|
- int packet_number = GetFramePacketNumber(current_input_buffer, current_input_size,
|
|
- data->input_buffer_read_offset);
|
|
-
|
|
- if (packet_number == -1) {
|
|
- return;
|
|
- }
|
|
-
|
|
- auto offset =
|
|
- xma::GetPacketFrameOffset(current_input_buffer + kBytesPerPacket * packet_number) +
|
|
- data->input_buffer_read_offset;
|
|
- if (offset == -1) {
|
|
- // No more frames.
|
|
- SwapInputBuffer(data);
|
|
- // TODO partial frames? end?
|
|
- REXAPU_ERROR("XmaContext {}: TODO partial frames? end?", id());
|
|
- assert_always("TODO");
|
|
- return;
|
|
- } else {
|
|
- data->input_buffer_read_offset = offset;
|
|
- }
|
|
- }
|
|
+ const int8_t headroom = (current_frame_remaining_subframes_ - subframes_to_write == 0)
|
|
+ ? data->output_buffer_padding
|
|
+ : 0;
|
|
|
|
- if (!ValidFrameOffset(current_input_buffer, current_input_size,
|
|
- data->input_buffer_read_offset)) {
|
|
- REXAPU_DEBUG("XmaContext {}: Invalid read offset {}!", id(),
|
|
- static_cast<uint32_t>(data->input_buffer_read_offset));
|
|
- SwapInputBuffer(data);
|
|
- return;
|
|
- }
|
|
+ remaining_subframe_blocks_in_output_buffer_ -= subframes_to_write + headroom;
|
|
+ current_frame_remaining_subframes_ -= subframes_to_write;
|
|
+}
|
|
|
|
- // Where are we in the buffer (in XMA jargon)
|
|
- std::tie(packet_idx, frame_idx) =
|
|
- GetFrameNumber(current_input_buffer, current_input_size, data->input_buffer_read_offset);
|
|
- // TODO handle
|
|
- assert_true(packet_idx >= 0);
|
|
- assert_true(frame_idx >= 0);
|
|
- packet = current_input_buffer + packet_idx * kBytesPerPacket;
|
|
- // frames that belong to this packet
|
|
- std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet);
|
|
- assert_true(frame_count >= 0); // TODO end
|
|
-
|
|
- PrepareDecoder(packet, data->sample_rate, bool(data->is_stereo));
|
|
-
|
|
- // Current frame is split to next packet:
|
|
- bool frame_is_split = frame_last_split && (frame_idx >= frame_count - 1);
|
|
-
|
|
- stream = BitStream(current_input_buffer, (packet_idx + 1) * kBitsPerPacket);
|
|
- stream.SetOffset(data->input_buffer_read_offset);
|
|
- // int frame_len;
|
|
- // int frame_len_partial
|
|
- split_frame_len_partial_ = static_cast<int>(stream.BitsRemaining());
|
|
- if (split_frame_len_partial_ >= 15) {
|
|
- split_frame_len_ = static_cast<int>(stream.Peek(15));
|
|
- } else {
|
|
- // assert_always();
|
|
- split_frame_len_ = xma::kMaxFrameLength + 1;
|
|
- }
|
|
- assert_true(frame_is_split == (split_frame_len_ > split_frame_len_partial_));
|
|
+int XmaContext::PrepareDecoder(int sample_rate, bool is_two_channel) {
|
|
+ sample_rate = GetSampleRate(sample_rate);
|
|
|
|
- // TODO fix bitstream copy
|
|
- std::memset(xma_frame_.data(), 0, xma_frame_.size());
|
|
+ uint32_t channels = is_two_channel ? 2 : 1;
|
|
+ if (av_context_->sample_rate != sample_rate ||
|
|
+ av_context_->channels != static_cast<int>(channels)) {
|
|
+ avcodec_close(av_context_);
|
|
+ av_free(av_context_);
|
|
+ av_context_ = avcodec_alloc_context3(av_codec_);
|
|
|
|
- {
|
|
- auto offset = stream.Copy(xma_frame_.data() + 1,
|
|
- std::min(split_frame_len_, split_frame_len_partial_));
|
|
- assert_true(offset < 8);
|
|
- split_frame_padding_start_ = static_cast<uint8_t>(offset);
|
|
- }
|
|
+ av_context_->sample_rate = sample_rate;
|
|
+ av_context_->channels = channels;
|
|
|
|
- if (frame_is_split) {
|
|
- // go to next xma packet of this stream
|
|
- packets_skip_ = xma::GetPacketSkipCount(packet) + 1;
|
|
- while (packets_skip_ > 0) {
|
|
- packets_skip_--;
|
|
- packet += kBytesPerPacket;
|
|
- packet_idx++;
|
|
- if (packet_idx >= current_input_packet_count) {
|
|
- if (!reuse_input_buffer) {
|
|
- // Last packet. Try setup once more.
|
|
- reuse_input_buffer = TrySetupNextLoop(data, true);
|
|
- }
|
|
- if (!reuse_input_buffer) {
|
|
- SwapInputBuffer(data);
|
|
- }
|
|
- return;
|
|
- }
|
|
- }
|
|
- // TODO guest might read this:
|
|
- data->input_buffer_read_offset = packet_idx * kBitsPerPacket;
|
|
- continue;
|
|
- }
|
|
+ if (avcodec_open2(av_context_, av_codec_, NULL) < 0) {
|
|
+ REXAPU_ERROR("XmaContext: Failed to reopen FFmpeg context");
|
|
+ return -1;
|
|
}
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
|
|
- av_packet_->data = xma_frame_.data();
|
|
- av_packet_->size =
|
|
- static_cast<int>(1 + ((split_frame_padding_start_ + split_frame_len_) / 8) +
|
|
- (((split_frame_padding_start_ + split_frame_len_) % 8) ? 1 : 0));
|
|
-
|
|
- auto padding_end = av_packet_->size * 8 - (8 + split_frame_padding_start_ + split_frame_len_);
|
|
- assert_true(padding_end < 8);
|
|
- xma_frame_[0] = ((split_frame_padding_start_ & 7) << 5) | ((padding_end & 7) << 2);
|
|
+void XmaContext::PreparePacket(uint32_t frame_size, uint32_t frame_padding) {
|
|
+ av_packet_->data = xma_frame_.data();
|
|
+ av_packet_->size = static_cast<int>(1 + ((frame_padding + frame_size) / 8) +
|
|
+ (((frame_padding + frame_size) % 8) ? 1 : 0));
|
|
|
|
- split_frame_len_ = 0;
|
|
- split_frame_len_partial_ = 0;
|
|
- split_frame_padding_start_ = 0;
|
|
+ auto padding_end = av_packet_->size * 8 - (8 + frame_padding + frame_size);
|
|
+ assert_true(padding_end < 8);
|
|
+ xma_frame_[0] = ((frame_padding & 7) << 5) | ((padding_end & 7) << 2);
|
|
+}
|
|
|
|
- auto ret = avcodec_send_packet(av_context_, av_packet_);
|
|
- if (ret < 0) {
|
|
- REXAPU_ERROR("XmaContext {}: Error sending packet for decoding", id());
|
|
- // TODO bail out
|
|
- assert_always();
|
|
- }
|
|
- ret = avcodec_receive_frame(av_context_, av_frame_);
|
|
- /*
|
|
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
- // TODO AVERROR_EOF???
|
|
- break;
|
|
- else
|
|
- */
|
|
- if (ret < 0) {
|
|
- REXAPU_ERROR("XmaContext {}: Error during decoding", id());
|
|
- assert_always();
|
|
- return; // TODO bail out
|
|
- }
|
|
- assert_true(ret == 0);
|
|
-
|
|
- {
|
|
- // copy over 1 frame
|
|
- // update input buffer read offset
|
|
-
|
|
- // assert(decoded_consumed_samples_ + kSamplesPerFrame <=
|
|
- // current_frame_.size());
|
|
- assert_true(av_context_->sample_fmt == AV_SAMPLE_FMT_FLTP);
|
|
- // assert_true(frame_is_split == (frame_idx == -1));
|
|
-
|
|
- // dump_raw(av_frame_, id());
|
|
- ConvertFrame((const uint8_t**)av_frame_->data, bool(data->is_stereo), raw_frame_.data());
|
|
- // decoded_consumed_samples_ += kSamplesPerFrame;
|
|
-
|
|
- auto byte_count = kBytesPerFrameChannel << data->is_stereo;
|
|
- assert_true(output_remaining_bytes >= byte_count);
|
|
- output_rb.Write(raw_frame_.data(), byte_count);
|
|
- output_remaining_bytes -= byte_count;
|
|
- data->output_buffer_write_offset = output_rb.write_offset() / 256;
|
|
-
|
|
- total_samples += id_ == 0 ? kSamplesPerFrame : 0;
|
|
-
|
|
- uint32_t offset = data->input_buffer_read_offset;
|
|
- // if (offset % (kBytesPerSample * 8) == 0) {
|
|
- // offset = xma::GetPacketFrameOffset(packet);
|
|
- //}
|
|
- offset =
|
|
- static_cast<uint32_t>(GetNextFrame(current_input_buffer, current_input_size, offset));
|
|
- // assert_true((offset == 0) ==
|
|
- // (frame_is_split || (frame_idx + 1 >= frame_count)));
|
|
- if (frame_idx + 1 >= frame_count) {
|
|
- // Skip to next packet (no split frame)
|
|
- packets_skip_ = xma::GetPacketSkipCount(packet) + 1;
|
|
- while (packets_skip_ > 0) {
|
|
- packets_skip_--;
|
|
- packet_idx++;
|
|
- if (packet_idx >= current_input_packet_count) {
|
|
- if (!reuse_input_buffer) {
|
|
- // Last packet. Try setup once more.
|
|
- reuse_input_buffer = TrySetupNextLoop(data, true);
|
|
- }
|
|
- if (!reuse_input_buffer) {
|
|
- SwapInputBuffer(data);
|
|
- }
|
|
- return;
|
|
- }
|
|
- }
|
|
- packet = current_input_buffer + packet_idx * kBytesPerPacket;
|
|
- offset = xma::GetPacketFrameOffset(packet) + packet_idx * kBitsPerPacket;
|
|
- }
|
|
- if (offset == 0 || frame_idx == -1) {
|
|
- // Next packet but we already skipped to it
|
|
- if (packet_idx >= current_input_packet_count) {
|
|
- // Buffer is fully used
|
|
- if (!reuse_input_buffer) {
|
|
- // Last packet. Try setup once more.
|
|
- reuse_input_buffer = TrySetupNextLoop(data, true);
|
|
- }
|
|
- if (!reuse_input_buffer) {
|
|
- SwapInputBuffer(data);
|
|
- }
|
|
- break;
|
|
- }
|
|
- offset = xma::GetPacketFrameOffset(packet) + packet_idx * kBitsPerPacket;
|
|
- }
|
|
- // TODO buffer bounds check
|
|
- assert_true(data->input_buffer_read_offset < offset);
|
|
- data->input_buffer_read_offset = offset;
|
|
- }
|
|
+bool XmaContext::DecodePacket(AVCodecContext* av_context, const AVPacket* av_packet,
|
|
+ AVFrame* av_frame) {
|
|
+ auto ret = avcodec_send_packet(av_context, av_packet);
|
|
+ if (ret < 0) {
|
|
+ REXAPU_ERROR("XmaContext {}: Error sending packet for decoding ({})", id(), ret);
|
|
+ return false;
|
|
}
|
|
+ ret = avcodec_receive_frame(av_context, av_frame);
|
|
|
|
- // assert_true((split_frame_len_ != 0) == (data->input_buffer_read_offset ==
|
|
- // 0));
|
|
-
|
|
- // The game will kick us again with a new output buffer later.
|
|
- // It's important that we only invalidate this if we actually wrote to it!!
|
|
- if (output_rb.write_offset() == output_rb.read_offset()) {
|
|
- data->output_buffer_valid = 0;
|
|
+ if (ret == AVERROR(EAGAIN)) {
|
|
+ return false;
|
|
}
|
|
+ if (ret < 0) {
|
|
+ REXAPU_ERROR("XmaContext {}: Error during decoding ({})", id(), ret);
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
}
|
|
|
|
-size_t XmaContext::GetNextFrame(uint8_t* block, size_t size, size_t bit_offset) {
|
|
- // offset = xma::GetPacketFrameOffset(packet);
|
|
- // TODO meh
|
|
- // auto next_packet = bit_offset - bit_offset % kBitsPerPacket +
|
|
- // kBitsPerPacket;
|
|
- auto packet_idx = GetFramePacketNumber(block, size, bit_offset);
|
|
-
|
|
- BitStream stream(block, size * 8);
|
|
- stream.SetOffset(bit_offset);
|
|
+void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|
+ SCOPE_profile_cpu_f("apu");
|
|
|
|
- if (stream.BitsRemaining() < 15) {
|
|
- return 0;
|
|
+ if (!data->IsAnyInputBufferValid()) {
|
|
+ return;
|
|
}
|
|
|
|
- uint64_t len = stream.Read(15);
|
|
- if ((len - 15) > stream.BitsRemaining()) {
|
|
- // assert_always("TODO");
|
|
- // *bit_offset = next_packet;
|
|
- // return false;
|
|
- // return next_packet;
|
|
- return 0;
|
|
- } else if (len >= xma::kMaxFrameLength) {
|
|
- // assert_always("TODO");
|
|
- // *bit_offset = next_packet;
|
|
- // return false;
|
|
- return 0;
|
|
- // return next_packet;
|
|
+ if (current_frame_remaining_subframes_ > 0) {
|
|
+ return;
|
|
}
|
|
|
|
- stream.Advance(len - (15 + 1));
|
|
- // Read the trailing bit to see if frames follow
|
|
- if (stream.Read(1) == 0) {
|
|
- return 0;
|
|
+ if (!data->IsCurrentInputBufferValid()) {
|
|
+ SwapInputBuffer(data);
|
|
+ if (!data->IsCurrentInputBufferValid()) {
|
|
+ return;
|
|
+ }
|
|
}
|
|
|
|
- bit_offset += len;
|
|
- if (packet_idx < GetFramePacketNumber(block, size, bit_offset)) {
|
|
- return 0;
|
|
+ uint8_t* current_input_buffer = GetCurrentInputBuffer(data);
|
|
+
|
|
+ input_buffer_.fill(0);
|
|
+
|
|
+ // Detect loop end frame before UpdateLoopStatus resets the offset.
|
|
+ bool is_loop_end_frame = false;
|
|
+ if (data->loop_count > 0) {
|
|
+ const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
|
|
+ is_loop_end_frame = (data->input_buffer_read_offset == loop_end);
|
|
}
|
|
- return bit_offset;
|
|
-}
|
|
|
|
-int XmaContext::GetFramePacketNumber(uint8_t* block, size_t size, size_t bit_offset) {
|
|
- size *= 8;
|
|
- if (bit_offset >= size) {
|
|
- // Not good :(
|
|
- assert_always();
|
|
- return -1;
|
|
+ UpdateLoopStatus(data);
|
|
+
|
|
+ if (!data->output_buffer_block_count) {
|
|
+ REXAPU_ERROR("XmaContext {}: Error - Received 0 for output_buffer_block_count!", id());
|
|
+ return;
|
|
}
|
|
|
|
- size_t byte_offset = bit_offset >> 3;
|
|
- size_t packet_number = byte_offset / kBytesPerPacket;
|
|
+ if (data->input_buffer_read_offset < kBitsPerPacketHeader) {
|
|
+ data->input_buffer_read_offset = kBitsPerPacketHeader;
|
|
+ }
|
|
|
|
- return (uint32_t)packet_number;
|
|
-}
|
|
+ const uint32_t current_input_size = GetCurrentInputBufferSize(data);
|
|
+ const uint32_t current_input_packet_count = current_input_size / kBytesPerPacket;
|
|
|
|
-std::tuple<int, int> XmaContext::GetFrameNumber(uint8_t* block, size_t size, size_t bit_offset) {
|
|
- auto packet_idx = GetFramePacketNumber(block, size, bit_offset);
|
|
+ const int16_t packet_index = GetPacketNumber(current_input_size, data->input_buffer_read_offset);
|
|
|
|
- if (packet_idx < 0 || (packet_idx + 1) * kBytesPerPacket > size) {
|
|
- assert_always();
|
|
- return {packet_idx, -2};
|
|
+ if (packet_index == -1) {
|
|
+ REXAPU_ERROR("XmaContext {}: Invalid packet index. Input read offset: {}", id(),
|
|
+ static_cast<uint32_t>(data->input_buffer_read_offset));
|
|
+ return;
|
|
}
|
|
|
|
- if (bit_offset == 0) {
|
|
- return {packet_idx, -1};
|
|
+ uint8_t* packet = current_input_buffer + (packet_index * kBytesPerPacket);
|
|
+ const uint32_t packet_first_frame_offset = xma::GetPacketFrameOffset(packet);
|
|
+ uint32_t relative_offset = data->input_buffer_read_offset % kBitsPerPacket;
|
|
+
|
|
+ if (relative_offset < packet_first_frame_offset) {
|
|
+ data->input_buffer_read_offset = (packet_index * kBitsPerPacket) + packet_first_frame_offset;
|
|
+ relative_offset = packet_first_frame_offset;
|
|
}
|
|
|
|
- uint8_t* packet = block + (packet_idx * kBytesPerPacket);
|
|
- auto first_frame_offset = xma::GetPacketFrameOffset(packet);
|
|
- BitStream stream(block, size * 8);
|
|
- stream.SetOffset(packet_idx * kBitsPerPacket + first_frame_offset);
|
|
+ const uint8_t skip_count = xma::GetPacketSkipCount(packet);
|
|
|
|
- int frame_idx = 0;
|
|
- while (true) {
|
|
- if (stream.BitsRemaining() < 15) {
|
|
- break;
|
|
+ // Full packet skip (0xFF) -- no new frames begin in this packet.
|
|
+ if (skip_count == 0xFF) {
|
|
+ uint32_t next_input_offset =
|
|
+ GetNextPacketReadOffset(current_input_buffer, packet_index + 1, current_input_packet_count);
|
|
+ if (next_input_offset == kBitsPerPacketHeader) {
|
|
+ SwapInputBuffer(data);
|
|
}
|
|
+ data->input_buffer_read_offset = next_input_offset;
|
|
+ return;
|
|
+ }
|
|
|
|
- if (stream.offset_bits() == bit_offset) {
|
|
- break;
|
|
- }
|
|
+ kPacketInfo packet_info = GetPacketInfo(packet, relative_offset);
|
|
+ const uint32_t packet_to_skip = skip_count + 1;
|
|
+ const uint32_t next_packet_index = packet_index + packet_to_skip;
|
|
|
|
- uint64_t size = stream.Read(15);
|
|
- if ((size - 15) > stream.BitsRemaining()) {
|
|
- // Last frame.
|
|
- break;
|
|
- } else if (size == 0x7FFF) {
|
|
- // Invalid frame (and last of this packet)
|
|
- break;
|
|
+ // Frame header split across packet boundary.
|
|
+ if (packet_info.current_frame_size_ == 0) {
|
|
+ const uint8_t* next_packet = GetNextPacket(data, next_packet_index, current_input_packet_count);
|
|
+ if (!next_packet) {
|
|
+ SwapInputBuffer(data);
|
|
+ return;
|
|
}
|
|
+ std::memcpy(input_buffer_.data(), packet + kBytesPerPacketHeader, kBytesPerPacketData);
|
|
+ std::memcpy(input_buffer_.data() + kBytesPerPacketData, next_packet + kBytesPerPacketHeader,
|
|
+ kBytesPerPacketData);
|
|
|
|
- stream.Advance(size - (15 + 1));
|
|
+ BitStream combined(input_buffer_.data(), (kBitsPerPacket - kBitsPerPacketHeader) * 2);
|
|
+ combined.SetOffset(relative_offset - kBitsPerPacketHeader);
|
|
|
|
- // Read the trailing bit to see if frames follow
|
|
- if (stream.Read(1) == 0) {
|
|
- break;
|
|
+ uint64_t frame_size = combined.Peek(kBitsPerFrameHeader);
|
|
+ if (frame_size == xma::kMaxFrameLength) {
|
|
+ data->error_status = 4;
|
|
+ return;
|
|
}
|
|
- frame_idx++;
|
|
+ packet_info.current_frame_size_ = static_cast<uint32_t>(frame_size);
|
|
}
|
|
- return {packet_idx, frame_idx};
|
|
-}
|
|
|
|
-std::tuple<int, bool> XmaContext::GetPacketFrameCount(uint8_t* packet) {
|
|
- auto first_frame_offset = xma::GetPacketFrameOffset(packet);
|
|
- if (first_frame_offset > kBitsPerPacket - kBitsPerHeader) {
|
|
- // frame offset is beyond packet end
|
|
- return {0, false};
|
|
- }
|
|
+ BitStream stream(current_input_buffer, (packet_index + 1) * kBitsPerPacket);
|
|
+ stream.SetOffset(data->input_buffer_read_offset);
|
|
|
|
- BitStream stream(packet, kBitsPerPacket);
|
|
- stream.SetOffset(first_frame_offset);
|
|
- int frame_count = 0;
|
|
+ const uint64_t bits_to_copy = GetAmountOfBitsToRead(static_cast<uint32_t>(stream.BitsRemaining()),
|
|
+ packet_info.current_frame_size_);
|
|
|
|
- while (true) {
|
|
- frame_count++;
|
|
- if (stream.BitsRemaining() < 15) {
|
|
- return {frame_count, true};
|
|
- }
|
|
+ if (bits_to_copy == 0) {
|
|
+ REXAPU_ERROR("XmaContext {}: There are no bits to copy!", id());
|
|
+ SwapInputBuffer(data);
|
|
+ return;
|
|
+ }
|
|
|
|
- uint64_t size = stream.Read(15);
|
|
- if ((size - 15) > stream.BitsRemaining()) {
|
|
- return {frame_count, true};
|
|
- } else if (size == 0x7FFF) {
|
|
- assert_always();
|
|
- return {frame_count, true};
|
|
+ if (packet_info.isLastFrameInPacket()) {
|
|
+ if (stream.BitsRemaining() < packet_info.current_frame_size_) {
|
|
+ const uint8_t* next_packet =
|
|
+ GetNextPacket(data, next_packet_index, current_input_packet_count);
|
|
+ if (!next_packet) {
|
|
+ data->error_status = 4;
|
|
+ return;
|
|
+ }
|
|
+ std::memcpy(input_buffer_.data() + kBytesPerPacketData, next_packet + kBytesPerPacketHeader,
|
|
+ kBytesPerPacketData);
|
|
}
|
|
+ }
|
|
|
|
- stream.Advance(size - (15 + 1));
|
|
+ std::memcpy(input_buffer_.data(), packet + kBytesPerPacketHeader, kBytesPerPacketData);
|
|
|
|
- if (stream.Read(1) == 0) {
|
|
- return {frame_count, false};
|
|
+ stream = BitStream(input_buffer_.data(), (kBitsPerPacket - kBitsPerPacketHeader) * 2);
|
|
+ stream.SetOffset(relative_offset - kBitsPerPacketHeader);
|
|
+
|
|
+ xma_frame_.fill(0);
|
|
+
|
|
+ const uint32_t padding_start =
|
|
+ static_cast<uint8_t>(stream.Copy(xma_frame_.data() + 1, packet_info.current_frame_size_));
|
|
+
|
|
+ raw_frame_.fill(0);
|
|
+
|
|
+ PrepareDecoder(data->sample_rate, bool(data->is_stereo));
|
|
+ PreparePacket(packet_info.current_frame_size_, padding_start);
|
|
+ if (DecodePacket(av_context_, av_packet_, av_frame_)) {
|
|
+ ConvertFrame(reinterpret_cast<const uint8_t**>(&av_frame_->data), bool(data->is_stereo),
|
|
+ raw_frame_.data());
|
|
+ current_frame_remaining_subframes_ = 4 << data->is_stereo;
|
|
+
|
|
+ // Loop end: limit output to subframes 0..loop_subframe_end.
|
|
+ if (is_loop_end_frame) {
|
|
+ loop_frame_output_limit_ = (data->loop_subframe_end + 1) << data->is_stereo;
|
|
+ } else {
|
|
+ loop_frame_output_limit_ = 0;
|
|
}
|
|
- }
|
|
-}
|
|
|
|
-int XmaContext::PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel) {
|
|
- // Sanity check: Packet metadata is always 1 for XMA2/0 for XMA
|
|
- assert_true((packet[2] & 0x7) == 1 || (packet[2] & 0x7) == 0);
|
|
+ // Loop start: skip leading subframes per loop_subframe_skip.
|
|
+ if (loop_start_skip_pending_) {
|
|
+ const uint8_t skip = data->loop_subframe_skip << data->is_stereo;
|
|
+ if (skip < current_frame_remaining_subframes_) {
|
|
+ current_frame_remaining_subframes_ -= skip;
|
|
+ }
|
|
+ loop_start_skip_pending_ = false;
|
|
+ }
|
|
+ }
|
|
|
|
- sample_rate = GetSampleRate(sample_rate);
|
|
+ // Compute where to go next.
|
|
+ if (!packet_info.isLastFrameInPacket()) {
|
|
+ const uint32_t next_frame_offset =
|
|
+ (data->input_buffer_read_offset + bits_to_copy) % kBitsPerPacket;
|
|
+ data->input_buffer_read_offset = (packet_index * kBitsPerPacket) + next_frame_offset;
|
|
+ return;
|
|
+ }
|
|
|
|
- // Re-initialize the context with new sample rate and channels.
|
|
- uint32_t channels = is_two_channel ? 2 : 1;
|
|
- if (av_context_->sample_rate != sample_rate || av_context_->channels != channels) {
|
|
- // We have to reopen the codec so it'll realloc whatever data it needs.
|
|
- // TODO(DrChat): Find a better way.
|
|
- avcodec_close(av_context_);
|
|
+ uint32_t next_input_offset =
|
|
+ GetNextPacketReadOffset(current_input_buffer, next_packet_index, current_input_packet_count);
|
|
|
|
- av_context_->sample_rate = sample_rate;
|
|
- av_context_->channels = channels;
|
|
+ if (next_input_offset == kBitsPerPacketHeader) {
|
|
+ SwapInputBuffer(data);
|
|
+ if (data->IsAnyInputBufferValid()) {
|
|
+ next_input_offset = xma::GetPacketFrameOffset(
|
|
+ memory()->TranslatePhysical(data->GetCurrentInputBufferAddress()));
|
|
|
|
- if (avcodec_open2(av_context_, av_codec_, NULL) < 0) {
|
|
- REXAPU_ERROR("XmaContext: Failed to reopen FFmpeg context");
|
|
- return -1;
|
|
+ if (next_input_offset > kMaxFrameSizeinBits) {
|
|
+ SwapInputBuffer(data);
|
|
+ return;
|
|
+ }
|
|
}
|
|
- return 1;
|
|
}
|
|
- return 0;
|
|
+ data->input_buffer_read_offset = next_input_offset;
|
|
}
|
|
|
|
void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel,
|
|
--
|
|
2.52.0.windows.1
|
|
|