[APU, SDL] Refactor sample submission
- Move sample conversion to SDL callback thread - Add early channel down-conversion
This commit is contained in:
parent
460b2b75d0
commit
cd631fc447
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_APU_CONVERSION_H_
|
||||||
|
#define XENIA_APU_CONVERSION_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "xenia/base/byte_order.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace apu {
|
||||||
|
namespace conversion {
|
||||||
|
|
||||||
|
inline void sequential_6_BE_to_interleaved_6_LE(float* output,
|
||||||
|
const float* input,
|
||||||
|
size_t ch_sample_count) {
|
||||||
|
for (size_t sample = 0; sample < ch_sample_count; sample++) {
|
||||||
|
for (size_t channel = 0; channel < 6; channel++) {
|
||||||
|
output[sample * 6 + channel] =
|
||||||
|
xe::byte_swap(input[channel * ch_sample_count + sample]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void sequential_6_BE_to_interleaved_2_LE(float* output,
|
||||||
|
const float* input,
|
||||||
|
size_t ch_sample_count) {
|
||||||
|
// 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 = xe::byte_swap(input[0 * ch_sample_count + sample]);
|
||||||
|
float fr = xe::byte_swap(input[1 * ch_sample_count + sample]);
|
||||||
|
float fc = xe::byte_swap(input[2 * ch_sample_count + sample]);
|
||||||
|
float br = xe::byte_swap(input[4 * ch_sample_count + sample]);
|
||||||
|
float bl = xe::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace conversion
|
||||||
|
} // namespace apu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif
|
|
@ -10,9 +10,13 @@
|
||||||
#include "xenia/apu/sdl/sdl_audio_driver.h"
|
#include "xenia/apu/sdl/sdl_audio_driver.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "xenia/apu/apu_flags.h"
|
#include "xenia/apu/apu_flags.h"
|
||||||
|
#include "xenia/apu/conversion.h"
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/helper/sdl/sdl_helper.h"
|
#include "xenia/helper/sdl/sdl_helper.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -46,41 +50,37 @@ bool SDLAudioDriver::Initialize() {
|
||||||
}
|
}
|
||||||
sdl_initialized_ = true;
|
sdl_initialized_ = true;
|
||||||
|
|
||||||
SDL_AudioCallback audio_callback = [](void* userdata, Uint8* stream,
|
SDL_AudioSpec desired_spec = {};
|
||||||
int len) -> void {
|
SDL_AudioSpec obtained_spec;
|
||||||
assert_true(len == frame_size_);
|
desired_spec.freq = frame_frequency_;
|
||||||
const auto driver = static_cast<SDLAudioDriver*>(userdata);
|
desired_spec.format = AUDIO_F32;
|
||||||
|
desired_spec.channels = frame_channels_;
|
||||||
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
|
desired_spec.samples = channel_samples_;
|
||||||
if (driver->frames_queued_.empty()) {
|
desired_spec.callback = SDLCallback;
|
||||||
memset(stream, 0, len);
|
desired_spec.userdata = this;
|
||||||
} else {
|
// Allow the hardware to decide between 5.1 and stereo
|
||||||
auto buffer = driver->frames_queued_.front();
|
int allowed_change = SDL_AUDIO_ALLOW_CHANNELS_CHANGE;
|
||||||
driver->frames_queued_.pop();
|
for (int i = 0; i < 2; i++) {
|
||||||
if (cvars::mute) {
|
sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &desired_spec,
|
||||||
memset(stream, 0, len);
|
&obtained_spec, allowed_change);
|
||||||
} else {
|
|
||||||
memcpy(stream, buffer, len);
|
|
||||||
}
|
|
||||||
driver->frames_unused_.push(buffer);
|
|
||||||
|
|
||||||
auto ret = driver->semaphore_->Release(1, nullptr);
|
|
||||||
assert_true(ret);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_AudioSpec wanted_spec = {};
|
|
||||||
wanted_spec.freq = frame_frequency_;
|
|
||||||
wanted_spec.format = AUDIO_F32;
|
|
||||||
wanted_spec.channels = frame_channels_;
|
|
||||||
wanted_spec.samples = channel_samples_;
|
|
||||||
wanted_spec.callback = audio_callback;
|
|
||||||
wanted_spec.userdata = this;
|
|
||||||
sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &wanted_spec, nullptr, 0);
|
|
||||||
if (sdl_device_id_ <= 0) {
|
if (sdl_device_id_ <= 0) {
|
||||||
XELOGE("SDL_OpenAudioDevice() failed.");
|
XELOGE("SDL_OpenAudioDevice() failed.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (obtained_spec.channels == 2 || obtained_spec.channels == 6) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the system is 4 or 7.1, let SDL convert
|
||||||
|
allowed_change = 0;
|
||||||
|
SDL_CloseAudioDevice(sdl_device_id_);
|
||||||
|
sdl_device_id_ = -1;
|
||||||
|
}
|
||||||
|
if (sdl_device_id_ <= 0) {
|
||||||
|
XELOGE("Failed to get a compatible SDL Audio Device.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sdl_device_channels_ = obtained_spec.channels;
|
||||||
|
|
||||||
SDL_PauseAudioDevice(sdl_device_id_, 0);
|
SDL_PauseAudioDevice(sdl_device_id_, 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -99,13 +99,7 @@ void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// interleave the data
|
std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float));
|
||||||
for (size_t index = 0, o = 0; index < channel_samples_; ++index) {
|
|
||||||
for (size_t channel = 0, table = 0; channel < frame_channels_;
|
|
||||||
++channel, table += channel_samples_) {
|
|
||||||
output_frame[o++] = xe::byte_swap(input_frame[table + index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> guard(frames_mutex_);
|
std::unique_lock<std::mutex> guard(frames_mutex_);
|
||||||
|
@ -133,6 +127,45 @@ void SDLAudioDriver::Shutdown() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
|
||||||
|
SCOPE_profile_cpu_f("apu");
|
||||||
|
if (!userdata || !stream) {
|
||||||
|
XELOGE("SDLAudioDriver::sdl_callback called with nullptr.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto driver = static_cast<SDLAudioDriver*>(userdata);
|
||||||
|
assert_true(len ==
|
||||||
|
sizeof(float) * channel_samples_ * driver->sdl_device_channels_);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
|
||||||
|
if (driver->frames_queued_.empty()) {
|
||||||
|
std::memset(stream, 0, len);
|
||||||
|
} else {
|
||||||
|
auto buffer = driver->frames_queued_.front();
|
||||||
|
driver->frames_queued_.pop();
|
||||||
|
if (cvars::mute) {
|
||||||
|
std::memset(stream, 0, len);
|
||||||
|
} else {
|
||||||
|
switch (driver->sdl_device_channels_) {
|
||||||
|
case 2:
|
||||||
|
conversion::sequential_6_BE_to_interleaved_2_LE(
|
||||||
|
reinterpret_cast<float*>(stream), buffer, channel_samples_);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
conversion::sequential_6_BE_to_interleaved_6_LE(
|
||||||
|
reinterpret_cast<float*>(stream), buffer, channel_samples_);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert_unhandled_case(driver->sdl_device_channels_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
driver->frames_unused_.push(buffer);
|
||||||
|
|
||||||
|
auto ret = driver->semaphore_->Release(1, nullptr);
|
||||||
|
assert_true(ret);
|
||||||
|
}
|
||||||
|
};
|
||||||
} // namespace sdl
|
} // namespace sdl
|
||||||
} // namespace apu
|
} // namespace apu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -32,10 +32,13 @@ class SDLAudioDriver : public AudioDriver {
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
static void SDLCallback(void* userdata, Uint8* stream, int len);
|
||||||
|
|
||||||
xe::threading::Semaphore* semaphore_ = nullptr;
|
xe::threading::Semaphore* semaphore_ = nullptr;
|
||||||
|
|
||||||
SDL_AudioDeviceID sdl_device_id_ = -1;
|
SDL_AudioDeviceID sdl_device_id_ = -1;
|
||||||
bool sdl_initialized_ = false;
|
bool sdl_initialized_ = false;
|
||||||
|
uint8_t sdl_device_channels_ = 0;
|
||||||
|
|
||||||
static const uint32_t frame_frequency_ = 48000;
|
static const uint32_t frame_frequency_ = 48000;
|
||||||
static const uint32_t frame_channels_ = 6;
|
static const uint32_t frame_channels_ = 6;
|
||||||
|
|
Loading…
Reference in New Issue