mirror of
https://github.com/niakw/Prospero.Eden-Encore.git
synced 2026-10-17 14:01:09 +02:00
122 lines
4.9 KiB
C++
122 lines
4.9 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "devices.h"
|
|
#include "preferences.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <stdexcept>
|
|
#include "audio_core/sink/null_sink.h"
|
|
#include "common/logging.h"
|
|
#include "native_audio.hpp"
|
|
#include "audio_out_init.h"
|
|
|
|
namespace Eden {
|
|
using namespace AudioCore::Sink;
|
|
using namespace ps5::native_audio;
|
|
|
|
AudioStream::AudioStream(Core::System& system, u32 channels, const std::string& name_, StreamType type)
|
|
: SinkStream{system, type} {
|
|
if (type == StreamType::In || (channels != 1 && channels != 2 && channels != 6))
|
|
throw std::invalid_argument("Unsupported AudioOut stream format");
|
|
name = name_;
|
|
system_channels = channels;
|
|
device_channels = 2;
|
|
SetRingSize(4);
|
|
if (!AudioOutReady()) throw std::runtime_error("sceAudioOutInit failed");
|
|
handle = sceAudioOutOpen(0xff, 0, 0, kAudioOutGrain, kAudioOutRate, kAudioOutStereoS16);
|
|
if (handle < 0) throw std::runtime_error("sceAudioOutOpen failed");
|
|
std::array<int, 8> volumes;
|
|
volumes.fill(AudioVolume(LoadPreferences()));
|
|
if (sceAudioOutSetVolume(handle, 3, volumes.data()) < 0) {
|
|
sceAudioOutClose(handle);
|
|
handle = -1;
|
|
throw std::runtime_error("sceAudioOutSetVolume failed");
|
|
}
|
|
try {
|
|
worker = std::jthread([this](std::stop_token stop) {
|
|
std::array<s16, kAudioOutGrain * 2> block{};
|
|
std::unique_lock lock(mutex);
|
|
while (!stop.stop_requested()) {
|
|
if (!wake.wait(lock, stop, [this] { return !IsPaused(); })) break;
|
|
ProcessAudioOutAndRender(block, kAudioOutGrain);
|
|
// AudioOut paces complete 256-frame, 48 kHz stereo blocks.
|
|
in_flight = true;
|
|
lock.unlock();
|
|
const int result = sceAudioOutOutput(handle, block.data());
|
|
lock.lock();
|
|
in_flight = false;
|
|
wake.notify_all();
|
|
if (result < 0) {
|
|
failed = true;
|
|
SignalPause();
|
|
LOG_ERROR(Audio_Sink, "PS5 AudioOut output failed");
|
|
break;
|
|
}
|
|
output_frames += kAudioOutGrain;
|
|
for (std::size_t frame = 0; frame < kAudioOutGrain; ++frame)
|
|
nonzero_frames += block[frame * 2] != 0 || block[frame * 2 + 1] != 0;
|
|
}
|
|
});
|
|
} catch (...) {
|
|
sceAudioOutClose(handle);
|
|
handle = -1;
|
|
throw;
|
|
}
|
|
}
|
|
AudioStream::~AudioStream() { Finalize(); }
|
|
void AudioStream::Start(bool) {
|
|
std::scoped_lock lock(mutex);
|
|
if (handle >= 0 && !failed) { paused = false; wake.notify_one(); }
|
|
}
|
|
void AudioStream::Stop() {
|
|
SignalPause();
|
|
std::unique_lock lock(mutex);
|
|
wake.wait(lock, [this] { return !in_flight; });
|
|
}
|
|
void AudioStream::Finalize() {
|
|
worker.request_stop();
|
|
wake.notify_all();
|
|
if (worker.joinable()) worker.join();
|
|
std::scoped_lock lock(mutex);
|
|
SignalPause();
|
|
if (handle >= 0) {
|
|
const int drain = sceAudioOutOutput(handle, nullptr);
|
|
const int close = sceAudioOutClose(handle);
|
|
LOG_INFO(Audio_Sink, "EDEN_AUDIO_PORT_CLOSED frames={} nonzero={} failed={} drain={} close={}",
|
|
output_frames, nonzero_frames, failed.load(), drain, close);
|
|
handle = -1;
|
|
}
|
|
}
|
|
void AudioStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
|
|
std::scoped_lock lock(mutex); // Publish PCM and its descriptor before the consumer runs.
|
|
if (buffer.frames == 0 || buffer.frames > 0x8000 || buffer.frames_played != 0 ||
|
|
samples.size() != buffer.frames * system_channels)
|
|
throw std::invalid_argument("Invalid PCM buffer dimensions");
|
|
if (failed || handle < 0) throw std::runtime_error("AudioOut stream is unavailable");
|
|
SinkStream::AppendBuffer(buffer, samples);
|
|
}
|
|
SinkStream* AudioSink::AcquireSinkStream(Core::System& system, u32 channels,
|
|
const std::string& name, StreamType type) {
|
|
system_channels = channels;
|
|
// Microphone capture is outside this output adapter.
|
|
SinkStreamPtr stream = type == StreamType::In ?
|
|
SinkStreamPtr{std::make_unique<NullSinkStreamImpl>(system, type)} :
|
|
SinkStreamPtr{std::make_unique<AudioStream>(system, channels, name, type)};
|
|
stream->SetDeviceVolume(device_volume);
|
|
stream->SetSystemVolume(system_volume);
|
|
auto* result = stream.get();
|
|
streams.push_back(std::move(stream));
|
|
return result;
|
|
}
|
|
void AudioSink::CloseStream(SinkStream* stream) {
|
|
std::erase_if(streams, [stream](const auto& item) { return item.get() == stream; });
|
|
}
|
|
void AudioSink::SetDeviceVolume(f32 value) {
|
|
device_volume = std::clamp(value, 0.0f, 1.0f);
|
|
for (auto& stream : streams) stream->SetDeviceVolume(device_volume);
|
|
}
|
|
void AudioSink::SetSystemVolume(f32 value) {
|
|
system_volume = std::clamp(value, 0.0f, 1.0f);
|
|
for (auto& stream : streams) stream->SetSystemVolume(system_volume);
|
|
}
|
|
}
|