Add setup_and_build.bat script

This commit is contained in:
salh
2026-04-20 06:43:35 +03:00
parent d57857d157
commit 26c84988e7
21 changed files with 2692 additions and 40 deletions
+130
View File
@@ -0,0 +1,130 @@
@echo off
setlocal enabledelayedexpansion
echo =========================================
echo Prerequisites Check
echo =========================================
:: Check CMake
cmake --version >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] CMake not found on PATH.
echo Please download and install CMake 3.25+ from https://cmake.org/download/
echo Make sure to add CMake to the system PATH during installation.
pause
exit /b 1
)
:: Check Ninja
ninja --version >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Ninja not found on PATH.
echo Please download Ninja from https://github.com/ninja-build/ninja/releases and add it to your PATH.
pause
exit /b 1
)
:: Check Clang
clang --version >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Clang not found on PATH.
echo Please download LLVM/Clang from https://github.com/llvm/llvm-project/releases or via Visual Studio Installer (C++ Clang tools) and add to PATH.
pause
exit /b 1
)
clang++ --version >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Clang++ not found on PATH.
echo Please ensure clang++ is available.
pause
exit /b 1
)
:: Check Windows SDK
set sdk_found=0
reg query "HKLM\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0" /v InstallationFolder >nul 2>&1
if !errorlevel! equ 0 set sdk_found=1
reg query "HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0" /v InstallationFolder >nul 2>&1
if !errorlevel! equ 0 set sdk_found=1
if !sdk_found! equ 0 (
echo [ERROR] Windows SDK 10.0.19041+ not found in registry.
echo Please install it via the Visual Studio Installer by selecting the "Windows 10 SDK (10.0.19041.0)" or newer under the "Desktop development with C++" workload.
pause
exit /b 1
)
echo [OK] All prerequisites found!
echo.
echo =========================================
echo Git Branch Check
echo =========================================
for /f "delims=" %%i in ('git rev-parse --abbrev-ref HEAD') do set CURRENT_BRANCH=%%i
echo Current branch: !CURRENT_BRANCH!
if /I "!CURRENT_BRANCH!"=="main" (
echo Switching from main to dev-test branch...
git checkout dev-test
if !errorlevel! neq 0 (
echo [ERROR] Failed to checkout dev-test branch.
pause
exit /b 1
)
) else (
echo Already on !CURRENT_BRANCH! branch or not on main.
)
echo.
echo =========================================
echo ISO Detection and Extraction
echo =========================================
set ISO_FILE=
for %%f in (*.iso) do (
set ISO_FILE=%%f
goto :found_iso
)
:found_iso
if "!ISO_FILE!"=="" (
echo [ERROR] No .iso file found in the current directory.
echo Please place the Ace Combat 6 ISO in this folder.
pause
exit /b 1
)
echo Found ISO: !ISO_FILE!
set EXTRACT_XISO_EXE=extract-xiso.exe
if not exist "!EXTRACT_XISO_EXE!" (
echo extract-xiso not found. Downloading...
powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://github.com/XboxDev/extract-xiso/releases/download/build-202310260309/extract-xiso-win32-x64.zip' -OutFile 'extract-xiso.zip'; Expand-Archive -Path 'extract-xiso.zip' -DestinationPath 'extract-xiso-temp' -Force; Move-Item -Path 'extract-xiso-temp\extract-xiso.exe' -Destination '.' -Force; Remove-Item 'extract-xiso.zip'; Remove-Item 'extract-xiso-temp' -Recurse -Force"
if not exist "!EXTRACT_XISO_EXE!" (
echo [ERROR] Failed to download or extract extract-xiso.
echo Please download it manually from https://github.com/XboxDev/extract-xiso/releases and place extract-xiso.exe in this folder.
pause
exit /b 1
)
)
set EXTRACT_DIR=assets
echo Extracting '!ISO_FILE!' to '!EXTRACT_DIR!' directory...
if not exist "!EXTRACT_DIR!" mkdir "!EXTRACT_DIR!"
!EXTRACT_XISO_EXE! -d "!EXTRACT_DIR!" "!ISO_FILE!"
if !errorlevel! neq 0 (
echo [ERROR] Failed to extract ISO.
pause
exit /b 1
)
echo [OK] Extraction complete!
echo.
echo =========================================
echo Building the Game
echo =========================================
cmake --build --preset win-amd64-relwithdebinfo
if !errorlevel! neq 0 (
echo [ERROR] Build failed.
pause
exit /b 1
)
echo [SUCCESS] Setup and Build completed successfully!
pause
exit /b 0
@@ -40,6 +40,57 @@ uint32_t CountBoundSamplers(
return count;
}
bool IsSamplerBound(const ac6::d3d::SamplerBinding& sampler) {
return sampler.mag_filter || sampler.min_filter || sampler.mip_filter ||
sampler.mip_level || sampler.border_color;
}
struct SamplerFilterSummary {
uint32_t point_min_count = 0;
uint32_t linear_min_count = 0;
uint32_t point_mip_count = 0;
uint32_t linear_mip_count = 0;
uint32_t anisotropic_count = 0;
uint32_t mip_clamp_count = 0;
uint32_t max_mip_level = 0;
};
SamplerFilterSummary SummarizeSamplerFiltering(
const std::array<ac6::d3d::SamplerBinding, ac6::d3d::kMaxSamplers>& samplers) {
constexpr uint32_t kTexfPoint = 1;
constexpr uint32_t kTexfLinear = 2;
constexpr uint32_t kTexfAnisotropic = 3;
SamplerFilterSummary summary;
for (const auto& sampler : samplers) {
if (!IsSamplerBound(sampler)) {
continue;
}
if (sampler.min_filter == kTexfPoint) {
++summary.point_min_count;
} else if (sampler.min_filter == kTexfLinear) {
++summary.linear_min_count;
}
if (sampler.mip_filter == kTexfPoint) {
++summary.point_mip_count;
} else if (sampler.mip_filter == kTexfLinear) {
++summary.linear_mip_count;
}
if (sampler.mag_filter == kTexfAnisotropic || sampler.min_filter == kTexfAnisotropic) {
++summary.anisotropic_count;
}
if (sampler.mip_level != 0) {
++summary.mip_clamp_count;
summary.max_mip_level = std::max(summary.max_mip_level, sampler.mip_level);
}
}
return summary;
}
void HashU32(uint64_t& hash, uint32_t value) {
constexpr uint64_t kFnvPrime = 1099511628211ull;
hash ^= value;
@@ -65,6 +116,20 @@ bool IsHalfResLike(const ac6::d3d::ShadowState& shadow_state,
shadow_state.viewport.height * 4 <= swap_height * 3;
}
bool IsQuarterResLike(const ac6::d3d::ShadowState& shadow_state,
const rex::system::GraphicsSwapSubmission* swap_submission) {
if (!swap_submission || !swap_submission->frontbuffer_width ||
!swap_submission->frontbuffer_height || !shadow_state.viewport.width ||
!shadow_state.viewport.height) {
return false;
}
const uint32_t swap_width = swap_submission->frontbuffer_width;
const uint32_t swap_height = swap_submission->frontbuffer_height;
return shadow_state.viewport.width * 2 <= swap_width &&
shadow_state.viewport.height * 2 <= swap_height;
}
bool IsLikelyUiPass(const ac6::d3d::FrameCaptureSummary& capture_summary,
const ac6::d3d::ShadowState& shadow_state,
const rex::system::GraphicsSwapSubmission* swap_submission) {
@@ -94,6 +159,19 @@ bool IsLikelyParticlePass(const ac6::d3d::FrameCaptureSummary& capture_summary,
shadow_state.depth_stencil == 0);
}
bool IsLikelyPointSpritePass(const ac6::d3d::FrameCaptureSummary& capture_summary,
const ac6::d3d::ShadowState& shadow_state) {
if (capture_summary.topology_pointlist == 0 || capture_summary.primitive_draw_count == 0) {
return false;
}
const bool mostly_point_sprites =
capture_summary.topology_pointlist * 2 >= capture_summary.primitive_draw_count;
const bool compact_stream_layout = CountBoundStreams(shadow_state.streams) <= 2;
const bool textured = CountNonZero(shadow_state.textures) != 0;
return mostly_point_sprites && compact_stream_layout && textured;
}
bool IsLikelyAdditive(const ac6::d3d::FrameCaptureSummary& capture_summary,
const ac6::d3d::ShadowState& shadow_state) {
if (shadow_state.depth_stencil != 0) {
@@ -138,8 +216,17 @@ RenderEventSignature BuildRenderEventSignature(
signature.resolve_count = capture_summary.resolve_count;
signature.indexed_draw_count = capture_summary.indexed_draw_count;
signature.primitive_draw_count = capture_summary.primitive_draw_count;
signature.topology_pointlist_count = capture_summary.topology_pointlist;
signature.texture_count = CountNonZero(shadow_state.textures);
signature.sampler_count = CountBoundSamplers(shadow_state.samplers);
const SamplerFilterSummary sampler_filters = SummarizeSamplerFiltering(shadow_state.samplers);
signature.point_min_sampler_count = sampler_filters.point_min_count;
signature.linear_min_sampler_count = sampler_filters.linear_min_count;
signature.point_mip_sampler_count = sampler_filters.point_mip_count;
signature.linear_mip_sampler_count = sampler_filters.linear_mip_count;
signature.anisotropic_sampler_count = sampler_filters.anisotropic_count;
signature.mip_clamp_sampler_count = sampler_filters.mip_clamp_count;
signature.max_sampler_mip_level = sampler_filters.max_mip_level;
signature.stream_count = CountBoundStreams(shadow_state.streams);
signature.fetch_constant_count = CountNonZero(shadow_state.texture_fetch_ptrs);
signature.shader_gpr_alloc = shadow_state.shader_gpr_alloc;
@@ -148,12 +235,18 @@ RenderEventSignature BuildRenderEventSignature(
signature.has_depth_stencil = shadow_state.depth_stencil != 0;
signature.has_resolve = capture_summary.resolve_count != 0;
signature.half_res_like = IsHalfResLike(shadow_state, swap_submission);
signature.quarter_res_like = IsQuarterResLike(shadow_state, swap_submission);
signature.post_process_like =
signature.has_resolve && !signature.has_depth_stencil;
signature.ui_like =
IsLikelyUiPass(capture_summary, shadow_state, swap_submission);
signature.particle_like =
IsLikelyParticlePass(capture_summary, shadow_state);
signature.point_sprite_like =
IsLikelyPointSpritePass(capture_summary, shadow_state);
signature.point_filtered_like =
sampler_filters.point_min_count != 0 || sampler_filters.point_mip_count != 0;
signature.mip_clamped_like = sampler_filters.mip_clamp_count != 0;
signature.additive_like =
IsLikelyAdditive(capture_summary, shadow_state);
@@ -197,6 +290,9 @@ std::string BuildSignatureTags(const RenderEventSignature& signature) {
if (signature.half_res_like) {
append("half_res");
}
if (signature.quarter_res_like) {
append("quarter_res");
}
if (signature.post_process_like) {
append("post");
}
@@ -206,6 +302,18 @@ std::string BuildSignatureTags(const RenderEventSignature& signature) {
if (signature.particle_like) {
append("particles");
}
if (signature.point_sprite_like) {
append("point_sprites");
}
if (signature.point_filtered_like) {
append("point_filter");
}
if (signature.mip_clamped_like) {
append("mip_clamp");
}
if (signature.anisotropic_sampler_count != 0) {
append("aniso");
}
if (signature.additive_like) {
append("additive");
}
@@ -15,6 +15,7 @@ enum class SignatureClass : uint8_t {
kPostProcess,
kUiComposite,
kParticles,
kPointSpriteEffects,
kClouds,
kSmoke,
kExplosions,
@@ -34,8 +35,16 @@ struct RenderEventSignature {
uint32_t resolve_count = 0;
uint32_t indexed_draw_count = 0;
uint32_t primitive_draw_count = 0;
uint32_t topology_pointlist_count = 0;
uint32_t texture_count = 0;
uint32_t sampler_count = 0;
uint32_t point_min_sampler_count = 0;
uint32_t linear_min_sampler_count = 0;
uint32_t point_mip_sampler_count = 0;
uint32_t linear_mip_sampler_count = 0;
uint32_t anisotropic_sampler_count = 0;
uint32_t mip_clamp_sampler_count = 0;
uint32_t max_sampler_mip_level = 0;
uint32_t stream_count = 0;
uint32_t fetch_constant_count = 0;
uint32_t shader_gpr_alloc = 0;
@@ -44,9 +53,13 @@ struct RenderEventSignature {
bool has_depth_stencil = false;
bool has_resolve = false;
bool half_res_like = false;
bool quarter_res_like = false;
bool post_process_like = false;
bool ui_like = false;
bool particle_like = false;
bool point_sprite_like = false;
bool point_filtered_like = false;
bool mip_clamped_like = false;
bool additive_like = false;
SignatureClass classification = SignatureClass::kUnknown;
};
+6 -2
View File
@@ -96,6 +96,7 @@ void AnalyzeFrameBoundary(
if (audio_timing) {
g_snapshot.audio_timing_valid = true;
g_snapshot.audio_consumed_frames = audio_timing->consumed_frames;
g_snapshot.audio_queued_played_frames = audio_timing->queued_played_frames;
g_snapshot.audio_submitted_tic = audio_timing->submitted_tic;
g_snapshot.audio_host_elapsed_tic = audio_timing->host_elapsed_tic;
g_snapshot.audio_startup_inflight_frames = audio_timing->startup_inflight_frames;
@@ -104,6 +105,7 @@ void AnalyzeFrameBoundary(
} else {
g_snapshot.audio_timing_valid = false;
g_snapshot.audio_consumed_frames = 0;
g_snapshot.audio_queued_played_frames = 0;
g_snapshot.audio_submitted_tic = 0;
g_snapshot.audio_host_elapsed_tic = 0;
g_snapshot.audio_startup_inflight_frames = 0;
@@ -122,11 +124,13 @@ void AnalyzeFrameBoundary(
if (ShouldLogSignature(g_snapshot)) {
REXLOG_INFO(
"AC6 backend signature frame={} class={} id={:016X} hits={} tags={} draws={} resolves={}",
"AC6 backend signature frame={} class={} id={:016X} hits={} tags={} draws={} resolves={} viewport={}x{} pointlist={}",
g_snapshot.frame_index, ToString(g_snapshot.latest_signature.classification),
g_snapshot.latest_signature.stable_id, g_snapshot.repeated_signature_count,
g_snapshot.latest_signature_tags, g_snapshot.capture_draw_count,
g_snapshot.capture_resolve_count);
g_snapshot.capture_resolve_count, g_snapshot.latest_signature.viewport_width,
g_snapshot.latest_signature.viewport_height,
g_snapshot.latest_signature.topology_pointlist_count);
}
}
@@ -56,6 +56,7 @@ struct BackendDiagnosticsSnapshot {
uint64_t guest_vblank_interval_ticks = 0;
uint64_t last_guest_vblank_tick = 0;
uint64_t audio_consumed_frames = 0;
uint64_t audio_queued_played_frames = 0;
uint64_t audio_submitted_tic = 0;
uint64_t audio_host_elapsed_tic = 0;
double host_frame_time_ms = 0.0;
@@ -11,6 +11,9 @@ SignatureClass ClassifySignature(const RenderEventSignature& signature) {
signature.viewport_width >= signature.viewport_height * 2) {
return SignatureClass::kMissileTrails;
}
if (signature.point_sprite_like) {
return SignatureClass::kPointSpriteEffects;
}
if (signature.half_res_like && signature.post_process_like &&
signature.sampler_count >= 4 && signature.fetch_constant_count >= 2) {
return SignatureClass::kClouds;
@@ -43,6 +46,8 @@ const char* ToString(const SignatureClass signature_class) {
return "ui_composite";
case SignatureClass::kParticles:
return "particles";
case SignatureClass::kPointSpriteEffects:
return "point_sprite_effects";
case SignatureClass::kClouds:
return "clouds";
case SignatureClass::kSmoke:
+25 -2
View File
@@ -69,6 +69,27 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
ImGui::Text("signature: %016llX hits=%u",
static_cast<unsigned long long>(diagnostics.latest_signature.stable_id),
diagnostics.repeated_signature_count);
const uint32_t signature_viewport_width = diagnostics.latest_signature.viewport_width;
const uint32_t signature_viewport_height = diagnostics.latest_signature.viewport_height;
const uint32_t viewport_scale_x = diagnostics.frontbuffer_width
? (signature_viewport_width * 100) /
diagnostics.frontbuffer_width
: 0;
const uint32_t viewport_scale_y = diagnostics.frontbuffer_height
? (signature_viewport_height * 100) /
diagnostics.frontbuffer_height
: 0;
ImGui::Text("signature viewport: %ux%u (%u%% x %u%% of frontbuffer)",
signature_viewport_width, signature_viewport_height,
viewport_scale_x, viewport_scale_y);
ImGui::Text("signature point-list / primitive draws: %u / %u",
diagnostics.latest_signature.topology_pointlist_count,
diagnostics.latest_signature.primitive_draw_count);
ImGui::Text("effect hints: half_res=%s quarter_res=%s point_sprites=%s additive=%s",
diagnostics.latest_signature.half_res_like ? "yes" : "no",
diagnostics.latest_signature.quarter_res_like ? "yes" : "no",
diagnostics.latest_signature.point_sprite_like ? "yes" : "no",
diagnostics.latest_signature.additive_like ? "yes" : "no");
ImGui::TextWrapped("signature tags: %s",
diagnostics.latest_signature_tags.empty()
? "none"
@@ -93,9 +114,11 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
ImGui::Text("audio underruns / dropped / silence inject: %u / %u / %u",
diagnostics.audio_underruns, diagnostics.audio_dropped_frames,
diagnostics.audio_silence_injections);
ImGui::Text("audio consumed frames / submitted tic / host tic: %llu / %llu / %llu",
ImGui::Text("audio consumed / queued-played / submitted tic: %llu / %llu / %llu",
static_cast<unsigned long long>(diagnostics.audio_consumed_frames),
static_cast<unsigned long long>(diagnostics.audio_submitted_tic),
static_cast<unsigned long long>(diagnostics.audio_queued_played_frames),
static_cast<unsigned long long>(diagnostics.audio_submitted_tic));
ImGui::Text("audio host tic: %llu",
static_cast<unsigned long long>(diagnostics.audio_host_elapsed_tic));
ImGui::Text("audio startup inflight / callback dispatch / throttle: %u / %u / %u",
diagnostics.audio_startup_inflight_frames,
@@ -62,6 +62,7 @@ struct AudioClientState {
uint32_t callback_arg{0};
uint32_t wrapped_callback_arg{0};
uint64_t next_sequence_number{1};
uint64_t queued_played_frames{0};
AudioDriverTelemetry telemetry{};
AudioClock clock{};
AudioDriver* driver{nullptr};
@@ -51,6 +51,7 @@ struct AudioTelemetrySnapshot {
struct AudioClientTimingSnapshot {
uint64_t consumed_samples{0};
uint64_t consumed_frames{0};
uint64_t queued_played_frames{0};
uint64_t submitted_tic{0};
uint64_t startup_cap_tic{0};
uint64_t synthetic_startup_tic{0};
+193 -16
View File
@@ -3,13 +3,45 @@
#pragma once
#include <cmath>
#include <cstdint>
#include <native/audio/render_driver_frame_layout.h>
#include <rex/platform.h>
#include <rex/types.h>
namespace rex::audio::conversion {
inline constexpr float kStereoDownmixCenterGain = 0.70710678f;
inline constexpr float kStereoDownmixSurroundGain = 0.5f;
inline constexpr float kStereoDownmixLfeGain = 0.0f;
inline constexpr float kStereoDownmixPeakHeadroom = 0.92f;
inline constexpr float kStereoDownmixNormalize =
1.0f / (1.0f + kStereoDownmixCenterGain + kStereoDownmixSurroundGain +
kStereoDownmixLfeGain);
inline float SanitizeGuestAudioSample(float sample) {
if (!std::isfinite(sample)) {
return 0.0f;
}
if (sample > 1.0f) {
return 1.0f;
}
if (sample < -1.0f) {
return -1.0f;
}
return sample;
}
#if REX_ARCH_AMD64
inline __m128 SanitizeGuestAudioSamples(__m128 samples) {
const __m128 ordered_mask = _mm_cmpord_ps(samples, samples);
const __m128 min_sample = _mm_set1_ps(-1.0f);
const __m128 max_sample = _mm_set1_ps(1.0f);
samples = _mm_and_ps(samples, ordered_mask);
return _mm_min_ps(max_sample, _mm_max_ps(min_sample, samples));
}
#endif
#if REX_ARCH_AMD64
inline void sequential_6_BE_to_interleaved_6_LE(float* output, const float* input,
size_t ch_sample_count) {
@@ -38,33 +70,108 @@ inline void sequential_6_BE_to_interleaved_2_LE(float* output, const float* inpu
const __m128i byte_swap_shuffle =
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
const __m128 half = _mm_set1_ps(0.5f);
const __m128 two_fifths = _mm_set1_ps(1.0f / 2.5f);
const __m128 center_gain = _mm_set1_ps(kStereoDownmixCenterGain);
const __m128 surround_gain = _mm_set1_ps(kStereoDownmixSurroundGain);
const __m128 lfe_gain = _mm_set1_ps(kStereoDownmixLfeGain);
const __m128 normalize = _mm_set1_ps(kStereoDownmixNormalize);
const __m128 peak_headroom = _mm_set1_ps(kStereoDownmixPeakHeadroom);
const __m128 sign_mask = _mm_set1_ps(-0.0f);
// put center on left and right, discard low frequency
// Use a dialogue-forward stereo fold-down. The old mapping mixed rears too
// heavily for cutscenes and could sound smeared on stereo playback.
for (size_t sample = 0; sample < ch_sample_count; sample += 4) {
// load 4 samples from 6 channels each
__m128 fl = _mm_loadu_ps(&input[0 * ch_sample_count + sample]);
__m128 fr = _mm_loadu_ps(&input[1 * ch_sample_count + sample]);
__m128 fc = _mm_loadu_ps(&input[2 * ch_sample_count + sample]);
__m128 lf = _mm_loadu_ps(&input[3 * ch_sample_count + sample]);
__m128 bl = _mm_loadu_ps(&input[4 * ch_sample_count + sample]);
__m128 br = _mm_loadu_ps(&input[5 * ch_sample_count + sample]);
// byte swap
fl = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fl), byte_swap_shuffle));
fr = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fr), byte_swap_shuffle));
fc = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fc), byte_swap_shuffle));
lf = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(lf), byte_swap_shuffle));
bl = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(bl), byte_swap_shuffle));
br = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(br), byte_swap_shuffle));
fl = SanitizeGuestAudioSamples(fl);
fr = SanitizeGuestAudioSamples(fr);
fc = SanitizeGuestAudioSamples(fc);
lf = SanitizeGuestAudioSamples(lf);
bl = SanitizeGuestAudioSamples(bl);
br = SanitizeGuestAudioSamples(br);
__m128 left = _mm_add_ps(
_mm_add_ps(fl, _mm_mul_ps(fc, center_gain)),
_mm_add_ps(_mm_mul_ps(bl, surround_gain), _mm_mul_ps(lf, lfe_gain)));
__m128 right = _mm_add_ps(
_mm_add_ps(fr, _mm_mul_ps(fc, center_gain)),
_mm_add_ps(_mm_mul_ps(br, surround_gain), _mm_mul_ps(lf, lfe_gain)));
left = _mm_mul_ps(left, normalize);
right = _mm_mul_ps(right, normalize);
// Apply a lightweight linked limiter instead of hard clipping. Mission
// mixes can stack enough combat layers to hit repeated peaks, which sounds
// like constant crackling when clipped.
const __m128 left_abs = _mm_andnot_ps(sign_mask, left);
const __m128 right_abs = _mm_andnot_ps(sign_mask, right);
const __m128 max_abs = _mm_max_ps(left_abs, right_abs);
const __m128 limiter_denominator = _mm_max_ps(max_abs, peak_headroom);
const __m128 limiter_scale = _mm_div_ps(peak_headroom, limiter_denominator);
left = _mm_mul_ps(left, limiter_scale);
right = _mm_mul_ps(right, limiter_scale);
__m128 center_halved = _mm_mul_ps(fc, half);
__m128 left = _mm_add_ps(_mm_add_ps(fl, bl), center_halved);
__m128 right = _mm_add_ps(_mm_add_ps(fr, br), center_halved);
left = _mm_mul_ps(left, two_fifths);
right = _mm_mul_ps(right, two_fifths);
_mm_storeu_ps(&output[sample * 2], _mm_unpacklo_ps(left, right));
_mm_storeu_ps(&output[(sample + 2) * 2], _mm_unpackhi_ps(left, right));
}
}
inline void interleaved_6_BE_to_interleaved_2_LE(float* output, const float* input,
size_t ch_sample_count) {
for (size_t sample = 0; sample < ch_sample_count; ++sample) {
float fl = rex::byte_swap(input[sample * 6 + 0]);
float fr = rex::byte_swap(input[sample * 6 + 1]);
float fc = rex::byte_swap(input[sample * 6 + 2]);
float lf = rex::byte_swap(input[sample * 6 + 3]);
float bl = rex::byte_swap(input[sample * 6 + 4]);
float br = rex::byte_swap(input[sample * 6 + 5]);
fl = SanitizeGuestAudioSample(fl);
fr = SanitizeGuestAudioSample(fr);
fc = SanitizeGuestAudioSample(fc);
lf = SanitizeGuestAudioSample(lf);
bl = SanitizeGuestAudioSample(bl);
br = SanitizeGuestAudioSample(br);
float left = (fl + (fc * kStereoDownmixCenterGain) + (bl * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float right = (fr + (fc * kStereoDownmixCenterGain) + (br * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float max_abs = left >= 0.0f ? left : -left;
float right_abs = right >= 0.0f ? right : -right;
if (right_abs > max_abs) {
max_abs = right_abs;
}
if (max_abs > kStereoDownmixPeakHeadroom) {
const float limiter_scale = kStereoDownmixPeakHeadroom / max_abs;
left *= limiter_scale;
right *= limiter_scale;
}
output[sample * 2] = left;
output[sample * 2 + 1] = right;
}
}
inline void render_driver_6_BE_to_interleaved_2_LE(float* output, const float* input,
size_t ch_sample_count) {
switch (ResolveRenderDriverFrameLayout(input, ch_sample_count)) {
case RenderDriverFrameLayout::kInterleaved:
interleaved_6_BE_to_interleaved_2_LE(output, input, ch_sample_count);
return;
case RenderDriverFrameLayout::kPlanar:
default:
sequential_6_BE_to_interleaved_2_LE(output, input, ch_sample_count);
return;
}
}
#else
inline void sequential_6_BE_to_interleaved_6_LE(float* output, const float* input,
size_t ch_sample_count) {
@@ -79,15 +186,85 @@ inline void sequential_6_BE_to_interleaved_2_LE(float* output, const float* inpu
// Default 5.1 channel mapping is fl, fr, fc, lf, bl, br
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-default-channel-mapping
for (size_t sample = 0; sample < ch_sample_count; sample++) {
// put center on left and right, discard low frequency
float fl = rex::byte_swap(input[0 * ch_sample_count + sample]);
float fr = rex::byte_swap(input[1 * ch_sample_count + sample]);
float fc = rex::byte_swap(input[2 * ch_sample_count + sample]);
float br = rex::byte_swap(input[4 * ch_sample_count + sample]);
float bl = rex::byte_swap(input[5 * ch_sample_count + sample]);
float center_halved = fc * 0.5f;
output[sample * 2] = (fl + bl + center_halved) * (1.0f / 2.5f);
output[sample * 2 + 1] = (fr + br + center_halved) * (1.0f / 2.5f);
float lf = rex::byte_swap(input[3 * ch_sample_count + sample]);
float bl = rex::byte_swap(input[4 * ch_sample_count + sample]);
float br = rex::byte_swap(input[5 * ch_sample_count + sample]);
fl = SanitizeGuestAudioSample(fl);
fr = SanitizeGuestAudioSample(fr);
fc = SanitizeGuestAudioSample(fc);
lf = SanitizeGuestAudioSample(lf);
bl = SanitizeGuestAudioSample(bl);
br = SanitizeGuestAudioSample(br);
float left = (fl + (fc * kStereoDownmixCenterGain) + (bl * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float right = (fr + (fc * kStereoDownmixCenterGain) + (br * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float max_abs = left >= 0.0f ? left : -left;
float right_abs = right >= 0.0f ? right : -right;
if (right_abs > max_abs) {
max_abs = right_abs;
}
if (max_abs > kStereoDownmixPeakHeadroom) {
const float limiter_scale = kStereoDownmixPeakHeadroom / max_abs;
left *= limiter_scale;
right *= limiter_scale;
}
output[sample * 2] = left;
output[sample * 2 + 1] = right;
}
}
inline void interleaved_6_BE_to_interleaved_2_LE(float* output, const float* input,
size_t ch_sample_count) {
for (size_t sample = 0; sample < ch_sample_count; sample++) {
float fl = rex::byte_swap(input[sample * 6 + 0]);
float fr = rex::byte_swap(input[sample * 6 + 1]);
float fc = rex::byte_swap(input[sample * 6 + 2]);
float lf = rex::byte_swap(input[sample * 6 + 3]);
float bl = rex::byte_swap(input[sample * 6 + 4]);
float br = rex::byte_swap(input[sample * 6 + 5]);
fl = SanitizeGuestAudioSample(fl);
fr = SanitizeGuestAudioSample(fr);
fc = SanitizeGuestAudioSample(fc);
lf = SanitizeGuestAudioSample(lf);
bl = SanitizeGuestAudioSample(bl);
br = SanitizeGuestAudioSample(br);
float left = (fl + (fc * kStereoDownmixCenterGain) + (bl * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float right = (fr + (fc * kStereoDownmixCenterGain) + (br * kStereoDownmixSurroundGain) +
(lf * kStereoDownmixLfeGain)) *
kStereoDownmixNormalize;
float max_abs = left >= 0.0f ? left : -left;
float right_abs = right >= 0.0f ? right : -right;
if (right_abs > max_abs) {
max_abs = right_abs;
}
if (max_abs > kStereoDownmixPeakHeadroom) {
const float limiter_scale = kStereoDownmixPeakHeadroom / max_abs;
left *= limiter_scale;
right *= limiter_scale;
}
output[sample * 2] = left;
output[sample * 2 + 1] = right;
}
}
inline void render_driver_6_BE_to_interleaved_2_LE(float* output, const float* input,
size_t ch_sample_count) {
switch (ResolveRenderDriverFrameLayout(input, ch_sample_count)) {
case RenderDriverFrameLayout::kInterleaved:
interleaved_6_BE_to_interleaved_2_LE(output, input, ch_sample_count);
return;
case RenderDriverFrameLayout::kPlanar:
default:
sequential_6_BE_to_interleaved_2_LE(output, input, ch_sample_count);
return;
}
}
#endif
@@ -0,0 +1,18 @@
// Native audio runtime
// Part of the AC6 Recompilation native foundation
#pragma once
#include <cstddef>
namespace rex::audio::conversion {
enum class RenderDriverFrameLayout : unsigned char {
kPlanar,
kInterleaved,
};
RenderDriverFrameLayout ResolveRenderDriverFrameLayout(const float* input, size_t ch_sample_count);
const char* ToString(RenderDriverFrameLayout layout);
} // namespace rex::audio::conversion
+376
View File
@@ -0,0 +1,376 @@
From 807931c9384a822ad0a54b3fbdec5f7af7a83bc1 Mon Sep 17 00:00:00 2001
From: Tom <1568512+tomcl7@users.noreply.github.com>
Date: Wed, 11 Mar 2026 17:43:12 -0400
Subject: [PATCH] fix(audio): frame queuing, XMA decoder sync completion, and
diagnostics
- Clamp queued_frames to 4-64 range, null validation in RegisterRenderDriverClient
- XMA work completion event, WriteRegister blocks until contexts finish
- XMABlockWhileInUse checks work_buffer_ptr to prevent hangs
---
include/rex/audio/audio_system.h | 5 +--
include/rex/audio/xma/context.h | 13 +++++++
src/audio/audio_system.cpp | 44 +++++++++++++++++++---
src/audio/sdl/sdl_audio_driver.cpp | 12 ++++++
src/audio/xma_context.cpp | 3 +-
src/audio/xma_decoder.cpp | 35 +++++++++--------
src/kernel/xboxkrnl/xboxkrnl_audio.cpp | 17 +++++++++
src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp | 4 ++
8 files changed, 106 insertions(+), 27 deletions(-)
diff --git a/include/native/audio/audio_system.h b/include/native/audio/audio_system.h
index cf67c344..6c6a72fb 100644
--- a/include/native/audio/audio_system.h
+++ b/include/native/audio/audio_system.h
@@ -66,13 +66,12 @@ class AudioSystem : public system::IAudioSystem {
AudioDriver** out_driver) = 0;
virtual void DestroyDriver(AudioDriver* driver) = 0;
- // TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64,
- // XAUDIO2_MAX_QUEUED_BUFFERS))
- // static const size_t kMaximumQueuedFrames = 64;
+ static constexpr size_t kMaximumQueuedFrames = 64;
memory::Memory* memory_ = nullptr;
runtime::Processor* processor_ = nullptr;
std::unique_ptr<XmaDecoder> xma_decoder_;
+ uint32_t queued_frames_;
std::atomic<bool> worker_running_ = {false};
system::object_ref<system::XHostThread> worker_thread_;
diff --git a/include/native/audio/xma/context.h b/include/native/audio/xma/context.h
index 75581831..47db366e 100644
--- a/include/native/audio/xma/context.h
+++ b/include/native/audio/xma/context.h
@@ -19,6 +19,7 @@
#include <rex/kernel.h>
#include <rex/memory.h>
+#include <rex/thread.h>
// XMA audio format:
// From research, XMA appears to be based on WMA Pro with
@@ -165,6 +166,17 @@ class XmaContext {
void set_is_allocated(bool is_allocated) { is_allocated_ = is_allocated; }
void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
+ void SignalWorkDone() {
+ if (work_completion_event_) {
+ work_completion_event_->Set();
+ }
+ }
+ void WaitForWorkDone() {
+ if (work_completion_event_) {
+ rex::thread::Wait(work_completion_event_.get(), false);
+ }
+ }
+
private:
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data, bool ignore_input_buffer_offset);
@@ -188,6 +200,7 @@ class XmaContext {
int PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel);
memory::Memory* memory_ = nullptr;
+ std::unique_ptr<rex::thread::Event> work_completion_event_;
uint32_t id_ = 0;
uint32_t guest_ptr_ = 0;
diff --git a/src/native/audio/audio_system.cpp b/src/native/audio/audio_system.cpp
index 7a5042a6..6b9767c9 100644
--- a/src/native/audio/audio_system.cpp
+++ b/src/native/audio/audio_system.cpp
@@ -24,7 +24,9 @@
#include <rex/thread.h>
#include <rex/cvar.h>
-REXCVAR_DEFINE_INT32(audio_maxqframes, 64, "Audio", "Adjust audio maximum queued frames");
+REXCVAR_DEFINE_INT32(
+ audio_maxqframes, 8, "Audio",
+ "Max buffered audio frames (range 4-64). Lower reduces latency but may cause stuttering.");
// As with normal Microsoft, there are like twelve different ways to access
// the audio APIs. Early games use XMA*() methods almost exclusively to touch
@@ -44,8 +46,12 @@ AudioSystem::AudioSystem(runtime::Processor* processor)
: memory_(processor->memory()), processor_(processor), worker_running_(false) {
std::memset(clients_, 0, sizeof(clients_));
+ queued_frames_ = std::min(
+ static_cast<uint32_t>(kMaximumQueuedFrames),
+ std::max(static_cast<uint32_t>(REXCVAR_GET(audio_maxqframes)), static_cast<uint32_t>(4)));
+
for (size_t i = 0; i < kMaximumClientCount; ++i) {
- client_semaphores_[i] = rex::thread::Semaphore::Create(0, REXCVAR_GET(audio_maxqframes));
+ client_semaphores_[i] = rex::thread::Semaphore::Create(0, queued_frames_);
assert_not_null(client_semaphores_[i]);
wait_handles_[i] = client_semaphores_[i].get();
}
@@ -89,6 +95,7 @@ void AudioSystem::WorkerThreadMain() {
Initialize();
// Main run loop.
+ uint32_t diag_pump_count = 0;
while (worker_running_) {
// These handles signify the number of submitted samples. Once we reach
// 64 samples, we wait until our audio backend releases a semaphore
@@ -96,10 +103,16 @@ void AudioSystem::WorkerThreadMain() {
auto result = rex::thread::WaitAny(wait_handles_, rex::countof(wait_handles_), true,
std::chrono::milliseconds(500));
if (result.first == rex::thread::WaitResult::kFailed) {
- // TODO: Assert?
+ REXAPU_WARN("AudioWorker: WaitAny failed");
continue;
}
+ if (result.first == rex::thread::WaitResult::kTimeout) {
+ if (diag_pump_count < 5) {
+ REXAPU_DEBUG("AudioWorker: WaitAny timed out (no semaphore signals)");
+ }
+ }
+
if (result.first == thread::WaitResult::kSuccess && result.second == kMaximumClientCount) {
// Shutdown event signaled.
if (paused_) {
@@ -121,10 +134,20 @@ void AudioSystem::WorkerThreadMain() {
global_lock.unlock();
if (client_callback) {
+ if (diag_pump_count < 10) {
+ REXAPU_DEBUG("AudioWorker: dispatching callback {:08X} with arg {:08X} for client {}",
+ client_callback, client_callback_arg, index);
+ }
SCOPE_profile_cpu_i("apu", "rex::audio::AudioSystem->client_callback");
uint64_t args[] = {client_callback_arg};
processor_->Execute(worker_thread_->thread_state(), client_callback, args,
rex::countof(args));
+ if (diag_pump_count < 10) {
+ REXAPU_DEBUG("AudioWorker: callback returned for client {}", index);
+ }
+ diag_pump_count++;
+ } else {
+ REXAPU_DEBUG("AudioWorker: semaphore signaled for client {} but callback is 0", index);
}
pumped = true;
@@ -188,13 +211,17 @@ void AudioSystem::Shutdown() {
}
X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, size_t* out_index) {
+ REXAPU_DEBUG("AudioSystem::RegisterClient: callback={:08X} callback_arg={:08X}", callback,
+ callback_arg);
auto global_lock = global_critical_region_.Acquire();
auto index = FindFreeClient();
assert_true(index >= 0);
+ REXAPU_DEBUG("AudioSystem::RegisterClient: using client index={} queued_frames={}", index,
+ queued_frames_);
auto client_semaphore = client_semaphores_[index].get();
- auto ret = client_semaphore->Release(REXCVAR_GET(audio_maxqframes), nullptr);
+ auto ret = client_semaphore->Release(queued_frames_, nullptr);
assert_true(ret);
AudioDriver* driver;
@@ -219,6 +246,13 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, s
void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
SCOPE_profile_cpu_f("apu");
+ static uint32_t submit_count = 0;
+ if (submit_count < 10) {
+ REXAPU_DEBUG("AudioSystem::SubmitFrame called: index={} samples_ptr={:08X}", index,
+ samples_ptr);
+ submit_count++;
+ }
+
auto global_lock = global_critical_region_.Acquire();
assert_true(index < kMaximumClientCount);
assert_true(clients_[index].driver != NULL);
@@ -296,7 +330,7 @@ bool AudioSystem::Restore(stream::ByteStream* stream) {
client.in_use = true;
auto client_semaphore = client_semaphores_[id].get();
- auto ret = client_semaphore->Release(REXCVAR_GET(audio_maxqframes), nullptr);
+ auto ret = client_semaphore->Release(queued_frames_, nullptr);
assert_true(ret);
AudioDriver* driver = nullptr;
diff --git a/src/native/audio/sdl/sdl_audio_driver.cpp b/src/native/audio/sdl/sdl_audio_driver.cpp
index 554c03bc..bd56f36c 100644
--- a/src/native/audio/sdl/sdl_audio_driver.cpp
+++ b/src/native/audio/sdl/sdl_audio_driver.cpp
@@ -110,6 +110,13 @@ void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) {
std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float));
+ static uint32_t sdl_submit_count = 0;
+ if (sdl_submit_count < 10) {
+ REXAPU_DEBUG("SDLAudioDriver::SubmitFrame: frame_ptr={:08X} queued_count={}", frame_ptr,
+ frames_queued_.size() + 1);
+ sdl_submit_count++;
+ }
+
{
std::unique_lock<std::mutex> guard(frames_mutex_);
frames_queued_.push(output_frame);
@@ -146,8 +153,13 @@ void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
assert_true(len ==
static_cast<int>(sizeof(float) * channel_samples_ * driver->sdl_device_channels_));
+ static uint32_t sdl_callback_count = 0;
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
+ if (sdl_callback_count < 10) {
+ REXAPU_DEBUG("SDLCallback: no frames queued (silence)");
+ sdl_callback_count++;
+ }
std::memset(stream, 0, len);
} else {
auto buffer = driver->frames_queued_.front();
diff --git a/src/native/audio/xma/context.cpp b/src/native/audio/xma/context.cpp
index 76e589e9..1b96d707 100644
--- a/src/native/audio/xma/context.cpp
+++ b/src/native/audio/xma/context.cpp
@@ -39,7 +39,8 @@ namespace rex::audio {
using stream::BitStream;
-XmaContext::XmaContext() = default;
+XmaContext::XmaContext()
+ : work_completion_event_(rex::thread::Event::CreateAutoResetEvent(false)) {}
XmaContext::~XmaContext() {
if (av_context_) {
diff --git a/src/native/audio/xma/decoder.cpp b/src/native/audio/xma/decoder.cpp
index f228692f..7780bac0 100644
--- a/src/native/audio/xma/decoder.cpp
+++ b/src/native/audio/xma/decoder.cpp
@@ -137,18 +137,16 @@ X_STATUS XmaDecoder::Setup(system::KernelState* kernel_state) {
}
void XmaDecoder::WorkerThreadMain() {
- uint32_t idle_loop_count = 0;
while (worker_running_) {
// Okay, let's loop through XMA contexts to find ones we need to decode!
bool did_work = false;
for (uint32_t n = 0; n < kContextCount && worker_running_; n++) {
XmaContext& context = contexts_[n];
- did_work = context.Work() || did_work;
-
- // TODO: Need thread safety to do this.
- // Probably not too important though.
- // registers_.current_context = n;
- // registers_.next_context = (n + 1) % kContextCount;
+ bool worked = context.Work();
+ if (worked) {
+ context.SignalWorkDone();
+ }
+ did_work = did_work || worked;
}
if (paused_) {
@@ -156,18 +154,11 @@ void XmaDecoder::WorkerThreadMain() {
resume_fence_.Wait();
}
- if (!did_work) {
- idle_loop_count++;
- } else {
- idle_loop_count = 0;
- }
-
- if (idle_loop_count > 500) {
- // Idle for an extended period. Introduce a 20ms wait.
- rex::thread::Wait(work_event_.get(), false, std::chrono::milliseconds(20));
+ if (did_work) {
+ continue;
}
-
- rex::thread::MaybeYield();
+ // No work done this iteration, block until signaled.
+ rex::thread::Wait(work_event_.get(), false);
}
}
@@ -292,6 +283,7 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
// The context ID is a bit in the range of the entire context array.
uint32_t base_context_id = (r - XmaRegister::Context0Kick) * 32;
+ uint32_t kicked_value = value;
for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) {
uint32_t context_id = base_context_id + i;
@@ -301,6 +293,13 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
}
// Signal the decoder thread to start processing.
work_event_->Set();
+ // Block until the worker finishes, so the game sees updated context data.
+ for (int i = 0; kicked_value && i < 32; ++i, kicked_value >>= 1) {
+ if (kicked_value & 1) {
+ uint32_t context_id = base_context_id + i;
+ contexts_[context_id].WaitForWorkDone();
+ }
+ }
} else if (r >= XmaRegister::Context0Lock && r <= XmaRegister::Context9Lock) {
// Context lock command.
// This requests a lock by flagging the context.
diff --git a/src/kernel/xboxkrnl/xboxkrnl_audio.cpp b/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
index 31407f26..dfd9db81 100644
--- a/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
+++ b/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
@@ -54,7 +54,17 @@ ppc_u32_result_t XAudioEnableDucker_entry(ppc_u32_t unk) {
ppc_u32_result_t XAudioRegisterRenderDriverClient_entry(ppc_pu32_t callback_ptr,
ppc_pu32_t driver_ptr) {
+ REXKRNL_DEBUG("XAudioRegisterRenderDriverClient called! callback_ptr={:08X} driver_ptr={:08X}",
+ callback_ptr.guest_address(), driver_ptr.guest_address());
+ if (!callback_ptr) {
+ return X_E_INVALIDARG;
+ }
+
uint32_t callback = callback_ptr[0];
+
+ if (!callback) {
+ return X_E_INVALIDARG;
+ }
uint32_t callback_arg = callback_ptr[1];
auto* audio_system = static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system());
@@ -82,6 +92,13 @@ ppc_u32_result_t XAudioSubmitRenderDriverFrame_entry(ppc_pvoid_t driver_ptr,
ppc_pvoid_t samples_ptr) {
assert_true((driver_ptr.guest_address() & 0xFFFF0000) == 0x41550000);
+ static uint32_t submit_krnl_count = 0;
+ if (submit_krnl_count < 10) {
+ REXKRNL_DEBUG("XAudioSubmitRenderDriverFrame: driver={:08X} samples={:08X}",
+ driver_ptr.guest_address(), samples_ptr.guest_address());
+ submit_krnl_count++;
+ }
+
auto* audio_system = static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system());
audio_system->SubmitFrame(driver_ptr.guest_address() & 0x0000FFFF, samples_ptr.guest_address());
diff --git a/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp b/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
index 17a4b4e1..3102f4fc 100644
--- a/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
+++ b/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
@@ -64,6 +64,7 @@ using rex::audio::XMA_CONTEXT_DATA;
// https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.xaudio2.xaudio2_buffer(v=vs.85).aspx
ppc_u32_result_t XMACreateContext_entry(ppc_pu32_t context_out_ptr) {
+ REXKRNL_DEBUG("XMACreateContext called!");
auto xma_decoder =
static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system())->xma_decoder();
uint32_t context_ptr = xma_decoder->AllocateContext();
@@ -341,6 +342,9 @@ ppc_u32_result_t XMABlockWhileInUse_entry(ppc_pvoid_t context_ptr) {
if (!context.input_buffer_0_valid && !context.input_buffer_1_valid) {
break;
}
+ if (!context.work_buffer_ptr) {
+ break;
+ }
rex::thread::Sleep(std::chrono::milliseconds(1));
} while (true);
return 0;
--
2.52.0.windows.1
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
From fba68dadef0d9026a795ba27f4d97841aa533009 Mon Sep 17 00:00:00 2001
From: Ryan Fisher <ryanfisher099@gmail.com>
Date: Sun, 22 Mar 2026 09:54:45 -0400
Subject: [PATCH] fix(audio): SDL startup and export XMA constants
- Export the XmaContext packet header and output size constants so Fable 2 can link against the XMA audio runtime correctly
- Harden SDL audio device startup by validating format detection, stereo fallback reopening, and device resume before playback begins
- Prevent SDL callback stalls when no audio frames are queued by feeding silence and checking stream writes instead of spinning indefinitely
---
src/audio/sdl/sdl_audio_driver.cpp | 62 +++++++++++++++++++++++++-----
src/audio/xma_context.cpp | 3 ++
2 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/src/native/audio/sdl/sdl_audio_driver.cpp b/src/native/audio/sdl/sdl_audio_driver.cpp
index 932ceaaf..46719f3a 100644
--- a/src/native/audio/sdl/sdl_audio_driver.cpp
+++ b/src/native/audio/sdl/sdl_audio_driver.cpp
@@ -9,6 +9,7 @@
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
*/
+#include <algorithm>
#include <array>
#include <cstring>
@@ -51,7 +52,7 @@ bool SDLAudioDriver::Initialize() {
sdl_initialized_ = true;
SDL_AudioSpec desired_spec = {};
- SDL_AudioSpec obtained_spec;
+ SDL_AudioSpec obtained_spec = {};
desired_spec.freq = frame_frequency_;
desired_spec.format = SDL_AUDIO_F32LE;
desired_spec.channels = frame_channels_;
@@ -59,18 +60,43 @@ bool SDLAudioDriver::Initialize() {
sdl_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired_spec,
SDLCallback, this);
if (!sdl_stream_) {
- REXAPU_ERROR("SDL_OpenAudioDevice() failed: {}", SDL_GetError());
+ REXAPU_ERROR("SDL_OpenAudioDeviceStream() failed: {}", SDL_GetError());
return false;
}
- SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(sdl_stream_), &obtained_spec, NULL);
+
+ SDL_AudioDeviceID sdl_device = SDL_GetAudioStreamDevice(sdl_stream_);
+ if (!sdl_device) {
+ REXAPU_ERROR("SDL_GetAudioStreamDevice() failed: {}", SDL_GetError());
+ return false;
+ }
+
+ if (!SDL_GetAudioDeviceFormat(sdl_device, &obtained_spec, NULL)) {
+ REXAPU_WARN("SDL_GetAudioDeviceFormat() failed: {}", SDL_GetError());
+ obtained_spec = desired_spec;
+ }
+
if (obtained_spec.channels == 2) {
SDL_DestroyAudioStream(sdl_stream_);
+ sdl_stream_ = nullptr;
desired_spec.channels = 2;
sdl_device_channels_ = 2;
sdl_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired_spec,
SDLCallback, this);
+ if (!sdl_stream_) {
+ REXAPU_ERROR("SDL_OpenAudioDeviceStream() stereo fallback failed: {}", SDL_GetError());
+ return false;
+ }
+ sdl_device = SDL_GetAudioStreamDevice(sdl_stream_);
+ if (!sdl_device) {
+ REXAPU_ERROR("SDL_GetAudioStreamDevice() failed after stereo fallback: {}", SDL_GetError());
+ return false;
+ }
+ }
+
+ if (!SDL_ResumeAudioDevice(sdl_device)) {
+ REXAPU_ERROR("SDL_ResumeAudioDevice() failed: {}", SDL_GetError());
+ return false;
}
- SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(sdl_stream_));
return true;
}
@@ -125,15 +151,21 @@ void SDLAudioDriver::Shutdown() {
}
void SDLAudioDriver::SDLCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
- int total_amount) {
+ [[maybe_unused]] int total_amount) {
SCOPE_profile_cpu_f("apu");
if (!userdata || !stream) {
REXAPU_ERROR("SDLAudioDriver::SDLCallback called with nullptr.");
return;
}
const auto driver = static_cast<SDLAudioDriver*>(userdata);
- const int len = static_cast<int>(sizeof(float) * channel_samples_ * driver->sdl_device_channels_);
- float* data = SDL_stack_alloc(float, len);
+ const int sample_count =
+ static_cast<int>(channel_samples_ * std::max<uint8_t>(driver->sdl_device_channels_, 1));
+ const int len = static_cast<int>(sizeof(float) * sample_count);
+ float* data = SDL_stack_alloc(float, sample_count);
+ if (!data) {
+ REXAPU_ERROR("SDLAudioDriver::SDLCallback failed to allocate {} samples", sample_count);
+ return;
+ }
while (additional_amount > 0) {
static uint32_t sdl_callback_count = 0;
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
@@ -142,10 +174,18 @@ void SDLAudioDriver::SDLCallback(void* userdata, SDL_AudioStream* stream, int ad
REXAPU_DEBUG("SDLCallback: no frames queued (silence)");
sdl_callback_count++;
}
+ std::memset(data, 0, len);
+ if (!SDL_PutAudioStreamData(stream, data, len)) {
+ REXAPU_ERROR("SDL_PutAudioStreamData() failed while filling silence: {}", SDL_GetError());
+ break;
+ }
+ additional_amount -= len;
} else {
auto buffer = driver->frames_queued_.front();
driver->frames_queued_.pop();
- if (!REXCVAR_GET(audio_mute)) {
+ if (REXCVAR_GET(audio_mute)) {
+ std::memset(data, 0, len);
+ } else {
switch (driver->sdl_device_channels_) {
case 2:
conversion::sequential_6_BE_to_interleaved_2_LE(data, buffer, channel_samples_);
@@ -157,7 +197,11 @@ void SDLAudioDriver::SDLCallback(void* userdata, SDL_AudioStream* stream, int ad
assert_unhandled_case(driver->sdl_device_channels_);
break;
}
- SDL_PutAudioStreamData(stream, data, len);
+ }
+ if (!SDL_PutAudioStreamData(stream, data, len)) {
+ REXAPU_ERROR("SDL_PutAudioStreamData() failed: {}", SDL_GetError());
+ driver->frames_unused_.push(buffer);
+ break;
}
driver->frames_unused_.push(buffer);
diff --git a/src/native/audio/xma/context.cpp b/src/native/audio/xma/context.cpp
index 9b4fb07d..eafb4105 100644
--- a/src/native/audio/xma/context.cpp
+++ b/src/native/audio/xma/context.cpp
@@ -40,6 +40,9 @@ namespace rex::audio {
using stream::BitStream;
+const uint32_t XmaContext::kBitsPerPacketHeader;
+const uint32_t XmaContext::kOutputMaxSizeBytes;
+
XmaContext::XmaContext()
: work_completion_event_(rex::thread::Event::CreateAutoResetEvent(false)) {}
--
2.52.0.windows.1
@@ -185,13 +185,14 @@ ppc_u32_result_t XAudioGetRenderDriverTic_entry(ppc_pvoid_t driver_ptr) {
REXKRNL_DEBUG(
"XAudioGetRenderDriverTic driver={:08X} guest_tic=0 internal_tic={} "
"submitted={} consumed={} queued_depth={} underruns={} callbacks={} "
"callback_throttles={} clock_samples={} clock_frames={} submitted_tic={} "
"callback_throttles={} clock_samples={} clock_frames={} queued_played={} submitted_tic={} "
"synthetic_tic={} startup_cap_tic={} startup_inflight={} callback_floor_tic={} "
"host_elapsed_tic={}",
driver_handle, internal_tic, telemetry.submitted_frames,
telemetry.consumed_frames, telemetry.queued_depth, telemetry.underrun_count,
telemetry.callback_dispatch_count, telemetry.callback_throttle_count,
timing.consumed_samples, timing.consumed_frames, timing.submitted_tic,
timing.consumed_samples, timing.consumed_frames, timing.queued_played_frames,
timing.submitted_tic,
timing.synthetic_startup_tic, timing.startup_cap_tic,
timing.startup_inflight_frames, timing.callback_floor_tic,
timing.host_elapsed_tic);
+3 -1
View File
@@ -3,6 +3,7 @@
add_library(rexaudio STATIC
audio_driver.cpp
audio_clock.cpp
render_driver_frame_layout.cpp
audio_runtime.cpp
audio_system.cpp
audio_trace.cpp
@@ -16,6 +17,7 @@ add_library(rex::audio ALIAS rexaudio)
if(WIN32)
target_sources(rexaudio PRIVATE
wasapi/wasapi_audio_driver.cpp
sdl/sdl_audio_driver.cpp
)
else()
target_sources(rexaudio PRIVATE
@@ -33,7 +35,7 @@ target_link_libraries(rexaudio
)
if(WIN32)
target_link_libraries(rexaudio PUBLIC ole32 avrt uuid)
target_link_libraries(rexaudio PUBLIC ole32 avrt uuid SDL3::SDL3)
else()
target_link_libraries(rexaudio PUBLIC SDL3::SDL3)
endif()
+24 -15
View File
@@ -12,10 +12,9 @@
#include <native/audio/audio_runtime.h>
#include <native/audio/sdl/sdl_audio_driver.h>
#if defined(_WIN32)
#include <native/audio/wasapi/wasapi_audio_driver.h>
#else
#include <native/audio/sdl/sdl_audio_driver.h>
#endif
#include <rex/cvar.h>
#include <rex/memory.h>
@@ -24,7 +23,7 @@
#if defined(_WIN32)
REXCVAR_DEFINE_STRING(audio_backend, "wasapi", "Audio", "Audio backend: wasapi")
.allowed({"wasapi"})
.allowed({"wasapi", "sdl"})
#else
REXCVAR_DEFINE_STRING(audio_backend, "sdl", "Audio", "Audio backend: sdl")
.allowed({"sdl"})
@@ -51,6 +50,9 @@ namespace {
std::unique_ptr<AudioDriver> CreateConfiguredDriver(memory::Memory* memory, AudioRuntime* runtime,
const size_t client_index) {
#if defined(_WIN32)
if (REXCVAR_GET(audio_backend) == "sdl") {
return std::make_unique<sdl::SdlAudioDriver>(memory, runtime, client_index);
}
return std::make_unique<wasapi::WasapiAudioDriver>(memory, runtime, client_index);
#else
return std::make_unique<sdl::SdlAudioDriver>(memory, runtime, client_index);
@@ -136,8 +138,8 @@ AudioDriverTelemetry MergeDriverTelemetry(const AudioClientState& client) {
return merged;
}
bool StartupConsumptionObserved(const AudioClientState& client) {
return client.clock.consumed_samples() != 0;
bool QueuedPlaybackObserved(const AudioClientState& client) {
return client.queued_played_frames != 0;
}
uint32_t StartupInflightFrames(const AudioClientState& client) {
@@ -179,7 +181,10 @@ bool ShouldThrottleStartupCallback(const AudioClientState& client,
*out_spacing_remaining_ms = 0;
}
if (StartupConsumptionObserved(client)) {
// Keep startup serialization active until the host has drained at least one
// queued render-driver frame. Host-injected underrun silence should advance
// tic timing, but it must not release callback pacing on its own.
if (QueuedPlaybackObserved(client)) {
return false;
}
@@ -244,6 +249,7 @@ AudioClientTimingSnapshot BuildTimingSnapshot(const AudioClientState& client) {
AudioClientTimingSnapshot snapshot;
snapshot.consumed_samples = client.clock.consumed_samples();
snapshot.consumed_frames = client.clock.consumed_frames();
snapshot.queued_played_frames = client.queued_played_frames;
snapshot.submitted_tic = SubmittedTicSamples(client);
snapshot.startup_cap_tic = StartupTicCapSamples(client);
snapshot.synthetic_startup_tic = ComputeStartupSyntheticTic(client);
@@ -562,6 +568,7 @@ bool AudioRuntime::ConsumeNextFrameForClient(const size_t index, AudioFrame* out
AudioFrame frame = client.queued_frames.front();
client.queued_frames.pop_front();
++client.queued_played_frames;
client.telemetry.queued_depth = static_cast<uint32_t>(client.queued_frames.size());
trace_buffer_.Record(AudioTraceSubsystem::kCore, AudioTraceEventType::kFrameConsumed,
static_cast<uint32_t>(index), frame.guest_submit_ptr,
@@ -765,6 +772,7 @@ size_t AudioRuntime::ConsumeQueuedFramesForClient(const size_t index, const size
while (consumed < max_frames && !client.queued_frames.empty()) {
const AudioFrame frame = client.queued_frames.front();
client.queued_frames.pop_front();
++client.queued_played_frames;
client.telemetry.last_consume_ticks = static_cast<uint64_t>(NextTickLocked());
client.telemetry.queued_depth = static_cast<uint32_t>(client.queued_frames.size());
client.telemetry.consumed_frames =
@@ -823,9 +831,10 @@ void AudioRuntime::WorkerThreadMain() {
// Check each active client and dispatch callbacks to fill queue to target
for (size_t i = 0; i < clients_.size(); ++i) {
// Before the first real playback consumption, keep callback lead tightly
// serialized so guest-side movie/cutscene workers cannot sprint ahead of
// the render driver on timeout wakes alone.
// Before the host drains the first queued render-driver frame, keep
// callback lead tightly serialized so guest-side movie/cutscene workers
// cannot sprint ahead on timeout wakes or underrun-silence progress
// alone.
uint32_t dispatch = 0;
while (true) {
uint32_t client_callback = 0;
@@ -846,7 +855,7 @@ void AudioRuntime::WorkerThreadMain() {
break;
}
target = EffectiveCallbackTargetQueueDepth(clients_[i]);
startup_mode = clients_[i].telemetry.callback_dispatch_count == 0;
startup_mode = !QueuedPlaybackObserved(clients_[i]);
callback_limit = startup_mode ? 1u : target;
if (dispatch >= callback_limit) {
break;
@@ -981,13 +990,13 @@ void AudioRuntime::WorkerThreadMain() {
const auto& c = clients_[i];
REXAPU_INFO(
"AudioRuntime startup: iter={} client={} queued={} target={} low_water={} "
"submitted={} consumed={} underruns={} callbacks={} tic={} synthetic_tic={} "
"submitted={} consumed={} queued_played={} underruns={} callbacks={} tic={} synthetic_tic={} "
"submitted_tic={} startup_cap_tic={} startup_inflight={} callback_throttles={} "
"callback_empty={} last_callback_frames={} last_callback_us={} peak={} drift_ms={:.1f}",
worker_iteration_count_, i, c.queued_frames.size(),
EffectiveCallbackTargetQueueDepth(c), EffectiveCallbackLowWaterFrames(c),
c.telemetry.submitted_frames, c.telemetry.consumed_frames, c.telemetry.underrun_count,
c.telemetry.callback_dispatch_count, ComputeRenderDriverTic(c),
c.telemetry.submitted_frames, c.telemetry.consumed_frames, c.queued_played_frames,
c.telemetry.underrun_count, c.telemetry.callback_dispatch_count, ComputeRenderDriverTic(c),
ComputeStartupSyntheticTic(c), SubmittedTicSamples(c), StartupTicCapSamples(c),
StartupInflightFrames(c), c.telemetry.callback_throttle_count,
c.telemetry.callback_empty_count, c.telemetry.last_callback_produced_frames,
@@ -1005,12 +1014,12 @@ void AudioRuntime::WorkerThreadMain() {
const auto& c = clients_[i];
REXAPU_DEBUG(
"AudioRuntime periodic: client={} queued={} target={} low_water={} submitted={} "
"consumed={} underruns={} callbacks={} tic={} synthetic_tic={} submitted_tic={} "
"consumed={} queued_played={} underruns={} callbacks={} tic={} synthetic_tic={} submitted_tic={} "
"startup_cap_tic={} startup_inflight={} callback_throttles={} callback_empty={} "
"last_callback_frames={} last_callback_us={} peak={} drift_ms={:.1f}",
i, c.queued_frames.size(), EffectiveCallbackTargetQueueDepth(c),
EffectiveCallbackLowWaterFrames(c), c.telemetry.submitted_frames,
c.telemetry.consumed_frames, c.telemetry.underrun_count,
c.telemetry.consumed_frames, c.queued_played_frames, c.telemetry.underrun_count,
c.telemetry.callback_dispatch_count, ComputeRenderDriverTic(c),
ComputeStartupSyntheticTic(c), SubmittedTicSamples(c), StartupTicCapSamples(c),
StartupInflightFrames(c), c.telemetry.callback_throttle_count,
@@ -0,0 +1,138 @@
// Native audio runtime
// Part of the AC6 Recompilation project
#include <native/audio/render_driver_frame_layout.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <native/math.h>
#include <rex/cvar.h>
#include <rex/logging.h>
#include <rex/types.h>
REXCVAR_DEFINE_STRING(audio_render_driver_layout, "auto", "Audio",
"Layout for XAudio render-driver frames: auto, planar, or interleaved")
.allowed({"auto", "planar", "interleaved"})
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
namespace rex::audio::conversion {
namespace {
std::atomic<int> g_detected_layout{0};
float DecodeSanitizedSample(const float* input, const size_t ch_sample_count, const size_t sample,
const size_t channel, const RenderDriverFrameLayout layout) {
const size_t index = layout == RenderDriverFrameLayout::kInterleaved
? (sample * 6) + channel
: (channel * ch_sample_count) + sample;
float value = rex::byte_swap(input[index]);
if (!std::isfinite(value)) {
return 0.0f;
}
return rex::clamp_float(value, -1.0f, 1.0f);
}
struct LayoutScore {
double continuity = 0.0;
double energy = 0.0;
};
struct LayoutDetectionResult {
RenderDriverFrameLayout layout = RenderDriverFrameLayout::kPlanar;
bool cacheable = false;
};
LayoutScore ScoreLayout(const float* input, const size_t ch_sample_count,
const RenderDriverFrameLayout layout) {
const size_t inspect_samples = std::min<size_t>(ch_sample_count, 64);
LayoutScore score{};
if (!input || inspect_samples < 2) {
return score;
}
for (size_t channel = 0; channel < 6; ++channel) {
float previous = DecodeSanitizedSample(input, ch_sample_count, 0, channel, layout);
score.energy += std::fabs(previous);
for (size_t sample = 1; sample < inspect_samples; ++sample) {
const float current =
DecodeSanitizedSample(input, ch_sample_count, sample, channel, layout);
score.continuity += std::fabs(current - previous);
score.energy += std::fabs(current);
previous = current;
}
}
return score;
}
LayoutDetectionResult DetectFrameLayout(const float* input, const size_t ch_sample_count) {
const LayoutScore planar = ScoreLayout(input, ch_sample_count, RenderDriverFrameLayout::kPlanar);
const LayoutScore interleaved =
ScoreLayout(input, ch_sample_count, RenderDriverFrameLayout::kInterleaved);
if (std::max(planar.energy, interleaved.energy) < 0.01) {
return {};
}
constexpr double kDecisionRatio = 0.75;
if (planar.continuity * kDecisionRatio < interleaved.continuity) {
return {RenderDriverFrameLayout::kPlanar, true};
}
if (interleaved.continuity * kDecisionRatio < planar.continuity) {
return {RenderDriverFrameLayout::kInterleaved, true};
}
return {planar.continuity <= interleaved.continuity ? RenderDriverFrameLayout::kPlanar
: RenderDriverFrameLayout::kInterleaved,
false};
}
} // namespace
RenderDriverFrameLayout ResolveRenderDriverFrameLayout(const float* input,
const size_t ch_sample_count) {
const auto& configured_layout = REXCVAR_GET(audio_render_driver_layout);
if (configured_layout == "planar") {
return RenderDriverFrameLayout::kPlanar;
}
if (configured_layout == "interleaved") {
return RenderDriverFrameLayout::kInterleaved;
}
const int cached_layout = g_detected_layout.load(std::memory_order_acquire);
if (cached_layout == 1) {
return RenderDriverFrameLayout::kPlanar;
}
if (cached_layout == 2) {
return RenderDriverFrameLayout::kInterleaved;
}
const LayoutDetectionResult detection = DetectFrameLayout(input, ch_sample_count);
if (!detection.cacheable) {
return detection.layout;
}
const RenderDriverFrameLayout detected_layout = detection.layout;
const int detected_value =
detected_layout == RenderDriverFrameLayout::kInterleaved ? 2 : 1;
const int previous =
g_detected_layout.exchange(detected_value, std::memory_order_acq_rel);
if (previous == 0) {
REXAPU_INFO("Audio render-driver layout auto-detected: {}", ToString(detected_layout));
}
return detected_layout;
}
const char* ToString(const RenderDriverFrameLayout layout) {
switch (layout) {
case RenderDriverFrameLayout::kPlanar:
return "planar";
case RenderDriverFrameLayout::kInterleaved:
return "interleaved";
default:
return "unknown";
}
}
} // namespace rex::audio::conversion
@@ -226,7 +226,7 @@ void SdlAudioDriver::FillStream(SDL_AudioStream* stream, int bytes_needed) {
if (buffer) {
if (!REXCVAR_GET(audio_mute)) {
conversion::sequential_6_BE_to_interleaved_2_LE(
conversion::render_driver_6_BE_to_interleaved_2_LE(
pending_output_frame_.data(), buffer, kRenderDriverTicSamplesPerFrame);
} else {
std::memset(pending_output_frame_.data(), 0, sizeof(float) * pending_output_frame_.size());
@@ -247,6 +247,12 @@ void SdlAudioDriver::FillStream(SDL_AudioStream* stream, int bytes_needed) {
++underrun_count_;
++silence_injections_;
bytes_needed -= std::min(bytes_needed, kOutputFrameBytes);
consumed_frames_.fetch_add(1, std::memory_order_relaxed);
if (runtime_) {
runtime_->ReportSamplesConsumedForClient(client_index_, kRenderDriverTicSamplesPerFrame);
runtime_->WakeWorker();
}
continue;
}
@@ -436,7 +436,7 @@ void WasapiAudioDriver::RenderThreadMain() {
if (buffer) {
if (!REXCVAR_GET(audio_mute)) {
conversion::sequential_6_BE_to_interleaved_2_LE(
conversion::render_driver_6_BE_to_interleaved_2_LE(
pending_output_frame_.data(), buffer, kRenderDriverTicSamplesPerFrame);
} else {
std::memset(pending_output_frame_.data(), 0, sizeof(float) * pending_output_frame_.size());
@@ -464,6 +464,10 @@ void WasapiAudioDriver::RenderThreadMain() {
++underrun_count_;
++silence_injections_;
std::memset(target, 0, sizeof(float) * 2 * frames_to_write);
if (runtime_) {
runtime_->ReportSamplesConsumedForClient(client_index_, frames_to_write);
runtime_->WakeWorker();
}
} else {
const size_t float_count = static_cast<size_t>(frames_to_write) * 2;
std::memcpy(target, pending_output_frame_.data() + pending_output_float_offset_,
+30
View File
@@ -697,8 +697,29 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
return;
}
auto skip_corrupt_packet = [&](const char* reason) {
data->error_status = 4;
last_error_status_ = static_cast<uint32_t>(data->error_status);
const uint32_t next_packet_index = packet_index + 1;
const bool next_packet_in_next_buffer = next_packet_index >= current_input_packet_count;
uint32_t next_input_offset =
GetNextPacketReadOffset(data, next_packet_index, current_input_packet_count);
if (next_packet_in_next_buffer || next_input_offset == kBitsPerPacketHeader) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
}
data->input_buffer_read_offset = next_input_offset;
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
log_decode_state(reason);
};
uint8_t* packet = current_input_buffer + (packet_index * kBytesPerPacket);
const uint32_t packet_first_frame_offset = xma::GetPacketFrameOffset(packet);
if (packet_first_frame_offset > kMaxFrameSizeinBits) {
skip_corrupt_packet("packet-frame-offset-invalid");
return;
}
uint32_t relative_offset = data->input_buffer_read_offset % kBitsPerPacket;
if (relative_offset < packet_first_frame_offset) {
@@ -761,6 +782,15 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
}
last_frame_size_bits_ = packet_info.current_frame_size_;
const uint32_t combined_payload_bits = (kBitsPerPacket - kBitsPerPacketHeader) * 2;
const uint32_t combined_relative_offset = relative_offset - kBitsPerPacketHeader;
if (packet_info.current_frame_size_ == 0 ||
combined_relative_offset > combined_payload_bits ||
packet_info.current_frame_size_ > (combined_payload_bits - combined_relative_offset)) {
skip_corrupt_packet("frame-size-out-of-range");
return;
}
BitStream stream(current_input_buffer, (packet_index + 1) * kBitsPerPacket);
stream.SetOffset(data->input_buffer_read_offset);