mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-20 12:39:13 +12:00
Get audio output working with LLE DSP (#419)
* Implement audio output * Semi-proper audio output * Add audio enable and vsync settings * Add audio enable and vsync settings * Optimize audio output a bit * Make max ring buffer timeout smaller * Make max ring buffer timeout smaller * Revert to spinlocking for audio sync * Sleep emulator thread if too many samples queued * Fix Teakra submodule breaking * Don't start audio device too soon * Fix IWYU errors * Fix compilation errors on GCC/Clang * Ignore std::hardware_destructive_interference_size on Android NDK * Fix more IWYU errors
This commit is contained in:
parent
8bca988b55
commit
d459cb1d6c
16 changed files with 396 additions and 15 deletions
143
src/core/audio/miniaudio_device.cpp
Normal file
143
src/core/audio/miniaudio_device.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include "audio/miniaudio_device.hpp"
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
MiniAudioDevice::MiniAudioDevice() : initialized(false), running(false), samples(nullptr) {}
|
||||
|
||||
void MiniAudioDevice::init(Samples& samples, bool safe) {
|
||||
this->samples = &samples;
|
||||
running = false;
|
||||
|
||||
// Probe for device and available backends and initialize audio
|
||||
ma_backend backends[ma_backend_null + 1];
|
||||
uint count = 0;
|
||||
|
||||
if (safe) {
|
||||
backends[0] = ma_backend_null;
|
||||
count = 1;
|
||||
} else {
|
||||
bool found = false;
|
||||
for (uint i = 0; i <= ma_backend_null; i++) {
|
||||
ma_backend backend = ma_backend(i);
|
||||
if (!ma_is_backend_enabled(backend)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
backends[count++] = backend;
|
||||
|
||||
// TODO: Make backend selectable here
|
||||
found = true;
|
||||
//count = 1;
|
||||
//backends[0] = backend;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
initialized = false;
|
||||
|
||||
Helpers::warn("No valid audio backend found\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ma_context_init(backends, count, nullptr, &context) != MA_SUCCESS) {
|
||||
initialized = false;
|
||||
|
||||
Helpers::warn("Unable to initialize audio context");
|
||||
return;
|
||||
}
|
||||
audioDevices.clear();
|
||||
|
||||
struct UserContext {
|
||||
MiniAudioDevice* miniAudio;
|
||||
ma_device_config& config;
|
||||
bool found = false;
|
||||
};
|
||||
UserContext userContext = {.miniAudio = this, .config = deviceConfig};
|
||||
|
||||
ma_context_enumerate_devices(
|
||||
&context,
|
||||
[](ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) -> ma_bool32 {
|
||||
if (deviceType != ma_device_type_playback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UserContext* userContext = reinterpret_cast<UserContext*>(pUserData);
|
||||
userContext->miniAudio->audioDevices.push_back(pInfo->name);
|
||||
|
||||
// TODO: Check if this is the device we want here
|
||||
userContext->config.playback.pDeviceID = &pInfo->id;
|
||||
userContext->found = true;
|
||||
return true;
|
||||
},
|
||||
&userContext
|
||||
);
|
||||
|
||||
if (!userContext.found) {
|
||||
Helpers::warn("MiniAudio: Device not found");
|
||||
}
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
// The 3DS outputs s16 stereo audio @ 32768 Hz
|
||||
deviceConfig.playback.format = ma_format_s16;
|
||||
deviceConfig.playback.channels = channelCount;
|
||||
deviceConfig.sampleRate = sampleRate;
|
||||
//deviceConfig.periodSizeInFrames = 64;
|
||||
//deviceConfig.periods = 16;
|
||||
deviceConfig.pUserData = this;
|
||||
deviceConfig.aaudio.usage = ma_aaudio_usage_game;
|
||||
deviceConfig.wasapi.noAutoConvertSRC = true;
|
||||
|
||||
deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) {
|
||||
auto self = reinterpret_cast<MiniAudioDevice*>(device->pUserData);
|
||||
s16* output = reinterpret_cast<ma_int16*>(out);
|
||||
|
||||
// Wait until there's enough samples to pop
|
||||
while (self->samples->size() < frameCount * channelCount) {
|
||||
// If audio output is disabled from the emulator thread, make sure that this callback will return and not hang
|
||||
if (!self->running) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->samples->pop(output, frameCount * channelCount);
|
||||
};
|
||||
|
||||
if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) {
|
||||
Helpers::warn("Unable to initialize audio device");
|
||||
initialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void MiniAudioDevice::start() {
|
||||
if (!initialized) {
|
||||
Helpers::warn("MiniAudio device not initialized, won't start");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the call to start if the device is already running
|
||||
if (!running) {
|
||||
if (ma_device_start(&device) == MA_SUCCESS) {
|
||||
running = true;
|
||||
} else {
|
||||
Helpers::warn("Failed to start audio device");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MiniAudioDevice::stop() {
|
||||
if (!initialized) {
|
||||
Helpers::warn("MiniAudio device not initialized, can't start");
|
||||
return;
|
||||
}
|
||||
|
||||
if (running) {
|
||||
running = false;
|
||||
|
||||
if (ma_device_stop(&device) != MA_SUCCESS) {
|
||||
Helpers::warn("Failed to stop audio device");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
#include "audio/teakra_core.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "services/dsp.hpp"
|
||||
|
||||
|
@ -51,10 +53,7 @@ TeakraDSP::TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService)
|
|||
ahbm.write32 = [&](u32 addr, u32 value) { *(u32*)&mem.getFCRAM()[addr - PhysicalAddrs::FCRAM] = value; };
|
||||
|
||||
teakra.SetAHBMCallback(ahbm);
|
||||
teakra.SetAudioCallback([=](std::array<s16, 2> sample) {
|
||||
//printf("%d %d\n", sample[0], sample[1]);
|
||||
// NOP for now
|
||||
});
|
||||
teakra.SetAudioCallback([](std::array<s16, 2> sample) { /* Do nothing */ });
|
||||
|
||||
// Set up event handlers. These handlers forward a hardware interrupt to the DSP service, which is responsible
|
||||
// For triggering the appropriate DSP kernel events
|
||||
|
@ -114,6 +113,36 @@ void TeakraDSP::reset() {
|
|||
running = false;
|
||||
loaded = false;
|
||||
signalledData = signalledSemaphore = false;
|
||||
|
||||
audioFrameIndex = 0;
|
||||
}
|
||||
|
||||
void TeakraDSP::setAudioEnabled(bool enable) {
|
||||
if (audioEnabled != enable) {
|
||||
audioEnabled = enable;
|
||||
|
||||
// Set the appropriate audio callback for Teakra
|
||||
if (audioEnabled) {
|
||||
teakra.SetAudioCallback([this](std::array<s16, 2> sample) {
|
||||
audioFrame[audioFrameIndex++] = sample[0];
|
||||
audioFrame[audioFrameIndex++] = sample[1];
|
||||
|
||||
// Push our samples at the end of an audio frame
|
||||
if (audioFrameIndex >= audioFrame.size()) {
|
||||
audioFrameIndex -= audioFrame.size();
|
||||
|
||||
// Wait until we've actually got room to do so
|
||||
while (sampleBuffer.size() + 2 > sampleBuffer.Capacity()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
}
|
||||
|
||||
sampleBuffer.push(audioFrame.data(), audioFrame.size());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
teakra.SetAudioCallback([](std::array<s16, 2> sample) { /* Do nothing */ });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp
|
||||
|
@ -313,4 +342,4 @@ void TeakraDSP::unloadComponent() {
|
|||
// Read the value and discard it, completing shutdown
|
||||
teakra.RecvData(2);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue