summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/audio
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/audio')
-rw-r--r--src/core/hle/service/audio/audout_u.cpp195
-rw-r--r--src/core/hle/service/audio/audout_u.h14
-rw-r--r--src/core/hle/service/audio/hwopus.cpp135
-rw-r--r--src/core/hle/service/audio/hwopus.h1
4 files changed, 247 insertions, 98 deletions
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 1dcd84d98..ab37c2a69 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -5,8 +5,7 @@
#include <array>
#include <vector>
#include "common/logging/log.h"
-#include "core/core_timing.h"
-#include "core/core_timing_util.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -14,17 +13,21 @@
namespace Service::Audio {
-/// Switch sample rate frequency
-constexpr u32 sample_rate{48000};
-/// TODO(st4rk): dynamic number of channels, as I think Switch has support
-/// to more audio channels (probably when Docked I guess)
-constexpr u32 audio_channels{2};
-/// TODO(st4rk): find a proper value for the audio_ticks
-constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 500)};
+namespace ErrCodes {
+enum {
+ ErrorUnknown = 2,
+ BufferCountExceeded = 8,
+};
+}
+
+constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
+constexpr int DefaultSampleRate{48000};
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
- IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) {
+ IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core)
+ : ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) {
+
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
@@ -32,66 +35,65 @@ public:
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
- {6, nullptr, "ContainsAudioOutBuffer"},
+ {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
- {9, nullptr, "GetAudioOutBufferCount"},
+ {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"},
};
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
- buffer_event =
- Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
-
- // Register event callback to update the Audio Buffer
- audio_event = CoreTiming::RegisterEvent(
- "IAudioOut::UpdateAudioBuffersCallback", [this](u64 userdata, int cycles_late) {
- UpdateAudioBuffersCallback();
- CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event);
- });
-
- // Start the audio event
- CoreTiming::ScheduleEvent(audio_ticks, audio_event);
- }
+ buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
- ~IAudioOut() {
- CoreTiming::UnscheduleEvent(audio_event, 0);
+ stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count,
+ [=]() { buffer_event->Signal(); });
}
private:
+ struct AudioBuffer {
+ u64_le next;
+ u64_le buffer;
+ u64_le buffer_capacity;
+ u64_le buffer_size;
+ u64_le offset;
+ };
+ static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size");
+
void GetAudioOutState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u32>(audio_out_state));
+ rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped));
}
void StartAudioOut(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
+
+ if (stream->IsPlaying()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown));
+ return;
+ }
- // Start audio
- audio_out_state = AudioState::Started;
+ audio_core.StartStream(stream);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StopAudioOut(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
-
- // Stop audio
- audio_out_state = AudioState::Stopped;
+ LOG_DEBUG(Service_Audio, "called");
- queue_keys.clear();
+ audio_core.StopStream(stream);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
@@ -99,101 +101,107 @@ private:
}
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
IPC::RequestParser rp{ctx};
- const u64 key{rp.Pop<u64>()};
- queue_keys.insert(queue_keys.begin(), key);
+ const auto& input_buffer{ctx.ReadBuffer()};
+ ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer),
+ "AudioBuffer input is an invalid size!");
+ AudioBuffer audio_buffer{};
+ std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer));
+ const u64 tag{rp.Pop<u64>()};
+
+ std::vector<u8> data(audio_buffer.buffer_size);
+ Memory::ReadBlock(audio_buffer.buffer, data.data(), data.size());
+
+ if (!audio_core.QueueBuffer(stream, tag, std::move(data))) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::BufferCountExceeded));
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
-
- // TODO(st4rk): This is how libtransistor currently implements the
- // GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address
- // is used to know which buffer should be filled with data and send again to the service
- // through AppendAudioOutBuffer. Check if this is the proper way to do it.
- u64 key{0};
-
- if (queue_keys.size()) {
- key = queue_keys.back();
- queue_keys.pop_back();
- }
+ LOG_DEBUG(Service_Audio, "called {}", ctx.Description());
+ IPC::RequestParser rp{ctx};
+ const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)};
+ const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
- ctx.WriteBuffer(&key, sizeof(u64));
+ std::vector<u64> tags{released_buffers};
+ tags.resize(max_count);
+ ctx.WriteBuffer(tags);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- // TODO(st4rk): This might be the total of released buffers, needs to be verified on
- // hardware
- rb.Push<u32>(static_cast<u32>(queue_keys.size()));
+ rb.Push<u32>(static_cast<u32>(released_buffers.size()));
}
- void UpdateAudioBuffersCallback() {
- if (audio_out_state != AudioState::Started) {
- return;
- }
-
- if (queue_keys.empty()) {
- return;
- }
+ void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+ IPC::RequestParser rp{ctx};
+ const u64 tag{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(stream->ContainsBuffer(tag));
+ }
- buffer_event->Signal();
+ void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
- enum class AudioState : u32 {
- Started,
- Stopped,
- };
+ AudioCore::AudioOut& audio_core;
+ AudioCore::StreamPtr stream;
- /// This is used to trigger the audio event callback that is going to read the samples from the
- /// audio_buffer list and enqueue the samples using the sink (audio_core).
- CoreTiming::EventType* audio_event;
+ AudoutParams audio_params{};
/// This is the evend handle used to check if the audio buffer was released
Kernel::SharedPtr<Kernel::Event> buffer_event;
-
- /// (st4rk): This is just a temporary workaround for the future implementation. Libtransistor
- /// uses the key as an address in the App, so we need to return when the
- /// GetReleasedAudioOutBuffer_1 is called, otherwise we'll run in problems, because
- /// libtransistor uses the key returned as an pointer.
- std::vector<u64> queue_keys;
-
- AudioState audio_out_state;
};
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
IPC::RequestParser rp{ctx};
- constexpr std::array<char, 15> audio_interface{{"AudioInterface"}};
- ctx.WriteBuffer(audio_interface);
+ ctx.WriteBuffer(DefaultDevice);
IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
rb.Push(RESULT_SUCCESS);
- // TODO(st4rk): We're currently returning only one audio interface (stringlist size). However,
- // it's highly possible to have more than one interface (despite that libtransistor requires
- // only one).
- rb.Push<u32>(1);
+ rb.Push<u32>(1); // Amount of audio devices
}
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
- if (!audio_out_interface) {
- audio_out_interface = std::make_shared<IAudioOut>();
+ ctx.WriteBuffer(DefaultDevice);
+ IPC::RequestParser rp{ctx};
+ auto params{rp.PopRaw<AudoutParams>()};
+ if (params.channel_count <= 2) {
+ // Mono does not exist for audout
+ params.channel_count = 2;
+ } else {
+ params.channel_count = 6;
}
+ if (!params.sample_rate) {
+ params.sample_rate = DefaultSampleRate;
+ }
+
+ // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl
+ // will likely need to be updated as well.
+ ASSERT_MSG(!audio_out_interface, "Unimplemented");
+ audio_out_interface = std::make_shared<IAudioOut>(std::move(params), *audio_core);
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(sample_rate);
- rb.Push<u32>(audio_channels);
+ rb.Push<u32>(DefaultSampleRate);
+ rb.Push<u32>(params.channel_count);
rb.Push<u32>(static_cast<u32>(PcmFormat::Int16));
- rb.Push<u32>(0); // This field is unknown
+ rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface);
}
@@ -203,6 +211,7 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") {
{2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
{3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
RegisterHandlers(functions);
+ audio_core = std::make_unique<AudioCore::AudioOut>();
}
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index 847d86aa6..e5c2184d5 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -4,6 +4,7 @@
#pragma once
+#include "audio_core/audio_out.h"
#include "core/hle/service/service.h"
namespace Kernel {
@@ -12,6 +13,18 @@ class HLERequestContext;
namespace Service::Audio {
+struct AudoutParams {
+ s32_le sample_rate;
+ u16_le channel_count;
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
+
+enum class AudioState : u32 {
+ Started,
+ Stopped,
+};
+
class IAudioOut;
class AudOutU final : public ServiceFramework<AudOutU> {
@@ -21,6 +34,7 @@ public:
private:
std::shared_ptr<IAudioOut> audio_out_interface;
+ std::unique_ptr<AudioCore::AudioOut> audio_core;
void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 844df382c..371cd4997 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
+#include <opus.h>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -9,19 +11,142 @@
namespace Service::Audio {
+struct OpusDeleter {
+ void operator()(void* ptr) const {
+ operator delete(ptr);
+ }
+};
+
+class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
+public:
+ IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate,
+ u32 channel_count)
+ : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
+ sample_rate(sample_rate), channel_count(channel_count) {
+ static const FunctionInfo functions[] = {
+ {0, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
+ {1, nullptr, "SetContext"},
+ {2, nullptr, "DecodeInterleavedForMultiStream"},
+ {3, nullptr, "SetContextForMultiStream"},
+ {4, nullptr, "Unknown4"},
+ {5, nullptr, "Unknown5"},
+ {6, nullptr, "Unknown6"},
+ {7, nullptr, "Unknown7"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
+ u32 consumed = 0;
+ u32 sample_count = 0;
+ std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
+ if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(ogniK): Use correct error code
+ rb.Push(ResultCode(-1));
+ return;
+ }
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(consumed);
+ rb.Push<u32>(sample_count);
+ ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
+ }
+
+ bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
+ std::vector<opus_int16>& output) {
+ size_t raw_output_sz = output.size() * sizeof(opus_int16);
+ if (sizeof(OpusHeader) > input.size())
+ return false;
+ OpusHeader hdr{};
+ std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
+ if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
+ return false;
+ }
+ auto frame = input.data() + sizeof(OpusHeader);
+ auto decoded_sample_count = opus_packet_get_nb_samples(
+ frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
+ static_cast<opus_int32>(sample_rate));
+ if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz)
+ return false;
+ auto out_sample_count =
+ opus_decode(decoder.get(), frame, hdr.sz, output.data(),
+ (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0);
+ if (out_sample_count < 0)
+ return false;
+ sample_count = out_sample_count;
+ consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
+ return true;
+ }
+
+ struct OpusHeader {
+ u32_be sz; // Needs to be BE for some odd reason
+ INSERT_PADDING_WORDS(1);
+ };
+ static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size");
+
+ std::unique_ptr<OpusDecoder, OpusDeleter> decoder;
+ u32 sample_rate;
+ u32 channel_count;
+};
+
+static size_t WorkerBufferSize(u32 channel_count) {
+ ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ return opus_decoder_get_size(static_cast<int>(channel_count));
+}
+
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ auto sample_rate = rp.Pop<u32>();
+ auto channel_count = rp.Pop<u32>();
+ ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
+ sample_rate == 12000 || sample_rate == 8000,
+ "Invalid sample rate");
+ ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
+ LOG_DEBUG(Audio, "called worker_buffer_sz={}", worker_buffer_sz);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0x4000);
+ rb.Push<u32>(worker_buffer_sz);
+}
+
+void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto sample_rate = rp.Pop<u32>();
+ auto channel_count = rp.Pop<u32>();
+ auto buffer_sz = rp.Pop<u32>();
+ LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
+ channel_count, buffer_sz);
+ ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
+ sample_rate == 12000 || sample_rate == 8000,
+ "Invalid sample rate");
+ ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+
+ size_t worker_sz = WorkerBufferSize(channel_count);
+ ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large");
+ std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
+ static_cast<OpusDecoder*>(operator new(worker_sz))};
+ if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(ogniK): Use correct error code
+ rb.Push(ResultCode(-1));
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate,
+ channel_count);
}
HwOpus::HwOpus() : ServiceFramework("hwopus") {
static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
+ {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
- {2, nullptr, "InitializeMultiStream"},
- {3, nullptr, "GetWorkBufferSizeMultiStream"},
+ {2, nullptr, "OpenOpusDecoderForMultiStream"},
+ {3, nullptr, "GetWorkBufferSizeForMultiStream"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index 090b8c825..5258d59f3 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -14,6 +14,7 @@ public:
~HwOpus() = default;
private:
+ void OpenOpusDecoder(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
};