// Copyright 2018 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include "audio_core/sdl2_sink.h" #include "audio_core/stream.h" #include "audio_core/time_stretch.h" #include "common/assert.h" #include "common/logging/log.h" //#include "common/settings.h" // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wimplicit-fallthrough" #endif #include #ifdef __clang__ #pragma clang diagnostic pop #endif namespace AudioCore { class SDLSinkStream final : public SinkStream { public: SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) : num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} { SDL_AudioSpec spec; spec.freq = sample_rate; spec.channels = static_cast(num_channels); spec.format = AUDIO_S16SYS; spec.samples = 4096; spec.callback = nullptr; SDL_AudioSpec obtained; if (output_device.empty()) { dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); } else { dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); } if (dev == 0) { LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); return; } SDL_PauseAudioDevice(dev, 0); } ~SDLSinkStream() override { if (dev == 0) { return; } SDL_CloseAudioDevice(dev); } void EnqueueSamples(u32 source_num_channels, const std::vector& samples) override { if (source_num_channels > num_channels) { // Downsample 6 channels to 2 ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); std::vector buf; buf.reserve(samples.size() * num_channels / source_num_channels); for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { // Downmixing implementation taken from the ATSC standard const s16 left{samples[i + 0]}; const s16 right{samples[i + 1]}; const s16 center{samples[i + 2]}; const s16 surround_left{samples[i + 4]}; const s16 surround_right{samples[i + 5]}; // Not used in the ATSC reference implementation [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; constexpr s32 clev{707}; // center mixing level coefficient constexpr s32 slev{707}; // surround mixing level coefficient buf.push_back(static_cast(left + (clev * center / 1000) + (slev * surround_left / 1000))); buf.push_back(static_cast(right + (clev * center / 1000) + (slev * surround_right / 1000))); } int ret = SDL_QueueAudio(dev, static_cast(buf.data()), static_cast(buf.size() * sizeof(s16))); if (ret < 0) LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); return; } int ret = SDL_QueueAudio(dev, static_cast(samples.data()), static_cast(samples.size() * sizeof(s16))); if (ret < 0) LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); } std::size_t SamplesInQueue(u32 channel_count) const override { if (dev == 0) return 0; return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); } void Flush() override { should_flush = true; } u32 GetNumChannels() const { return num_channels; } private: SDL_AudioDeviceID dev = 0; u32 num_channels{}; std::atomic should_flush{}; TimeStretcher time_stretch; }; SDLSink::SDLSink(std::string_view target_device_name) { if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); return; } } if (target_device_name != auto_device_name && !target_device_name.empty()) { output_device = target_device_name; } else { output_device.clear(); } } SDLSink::~SDLSink() = default; SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { sink_streams.push_back( std::make_unique(sample_rate, num_channels, output_device)); return *sink_streams.back(); } std::vector ListSDLSinkDevices() { std::vector device_list; if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); return {}; } } const int device_count = SDL_GetNumAudioDevices(0); for (int i = 0; i < device_count; ++i) { device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); } return device_list; } } // namespace AudioCore