|
@@ -2,35 +2,163 @@
|
|
|
// Licensed under GPLv2 or any later version
|
|
// Licensed under GPLv2 or any later version
|
|
|
// Refer to the license.txt file included.
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
|
|
+#include <vector>
|
|
|
#include "common/logging/log.h"
|
|
#include "common/logging/log.h"
|
|
|
|
|
+#include "core/core_timing.h"
|
|
|
#include "core/hle/ipc_helpers.h"
|
|
#include "core/hle/ipc_helpers.h"
|
|
|
|
|
+#include "core/hle/kernel/event.h"
|
|
|
#include "core/hle/kernel/hle_ipc.h"
|
|
#include "core/hle/kernel/hle_ipc.h"
|
|
|
#include "core/hle/service/audio/audout_u.h"
|
|
#include "core/hle/service/audio/audout_u.h"
|
|
|
|
|
|
|
|
namespace Service {
|
|
namespace Service {
|
|
|
namespace Audio {
|
|
namespace 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>(BASE_CLOCK_RATE / 500)};
|
|
|
|
|
+
|
|
|
class IAudioOut final : public ServiceFramework<IAudioOut> {
|
|
class IAudioOut final : public ServiceFramework<IAudioOut> {
|
|
|
public:
|
|
public:
|
|
|
- IAudioOut() : ServiceFramework("IAudioOut") {
|
|
|
|
|
|
|
+ IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(Stopped) {
|
|
|
static const FunctionInfo functions[] = {
|
|
static const FunctionInfo functions[] = {
|
|
|
{0x0, nullptr, "GetAudioOutState"},
|
|
{0x0, nullptr, "GetAudioOutState"},
|
|
|
- {0x1, nullptr, "StartAudioOut"},
|
|
|
|
|
- {0x2, nullptr, "StopAudioOut"},
|
|
|
|
|
- {0x3, nullptr, "AppendAudioOutBuffer_1"},
|
|
|
|
|
- {0x4, nullptr, "RegisterBufferEvent"},
|
|
|
|
|
- {0x5, nullptr, "GetReleasedAudioOutBuffer_1"},
|
|
|
|
|
|
|
+ {0x1, &IAudioOut::StartAudioOut, "StartAudioOut"},
|
|
|
|
|
+ {0x2, &IAudioOut::StopAudioOut, "StopAudioOut"},
|
|
|
|
|
+ {0x3, &IAudioOut::AppendAudioOutBuffer_1, "AppendAudioOutBuffer_1"},
|
|
|
|
|
+ {0x4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
|
|
|
|
|
+ {0x5, &IAudioOut::GetReleasedAudioOutBuffer_1, "GetReleasedAudioOutBuffer_1"},
|
|
|
{0x6, nullptr, "ContainsAudioOutBuffer"},
|
|
{0x6, nullptr, "ContainsAudioOutBuffer"},
|
|
|
{0x7, nullptr, "AppendAudioOutBuffer_2"},
|
|
{0x7, nullptr, "AppendAudioOutBuffer_2"},
|
|
|
{0x8, nullptr, "GetReleasedAudioOutBuffer_2"},
|
|
{0x8, nullptr, "GetReleasedAudioOutBuffer_2"},
|
|
|
};
|
|
};
|
|
|
RegisterHandlers(functions);
|
|
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);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
~IAudioOut() = default;
|
|
~IAudioOut() = default;
|
|
|
|
|
+
|
|
|
|
|
+private:
|
|
|
|
|
+ void StartAudioOut(Kernel::HLERequestContext& ctx) {
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+
|
|
|
|
|
+ // start audio
|
|
|
|
|
+ audio_out_state = Started;
|
|
|
|
|
+
|
|
|
|
|
+ IPC::RequestBuilder rb{ctx, 2};
|
|
|
|
|
+ rb.Push(RESULT_SUCCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void StopAudioOut(Kernel::HLERequestContext& ctx) {
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+
|
|
|
|
|
+ // stop audio
|
|
|
|
|
+ audio_out_state = Stopped;
|
|
|
|
|
+
|
|
|
|
|
+ queue_keys.clear();
|
|
|
|
|
+
|
|
|
|
|
+ IPC::RequestBuilder rb{ctx, 2};
|
|
|
|
|
+ rb.Push(RESULT_SUCCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+
|
|
|
|
|
+ IPC::RequestBuilder rb{ctx, 2, 1};
|
|
|
|
|
+ rb.Push(RESULT_SUCCESS);
|
|
|
|
|
+ rb.PushCopyObjects(buffer_event);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void AppendAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+ IPC::RequestParser rp{ctx};
|
|
|
|
|
+
|
|
|
|
|
+ u64 key = rp.Pop<u64>();
|
|
|
|
|
+
|
|
|
|
|
+ queue_keys.insert(queue_keys.begin(), key);
|
|
|
|
|
+
|
|
|
|
|
+ IPC::RequestBuilder rb{ctx, 2};
|
|
|
|
|
+ rb.Push(RESULT_SUCCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void GetReleasedAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+
|
|
|
|
|
+ const auto& buffer = ctx.BufferDescriptorB()[0];
|
|
|
|
|
+
|
|
|
|
|
+ // 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();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Memory::WriteBlock(buffer.Address(), &key, sizeof(u64));
|
|
|
|
|
+
|
|
|
|
|
+ IPC::RequestBuilder 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()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void UpdateAudioBuffersCallback() {
|
|
|
|
|
+
|
|
|
|
|
+ if (audio_out_state != Started) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (queue_keys.empty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ buffer_event->Signal();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ enum AudioState : u32 {
|
|
|
|
|
+ Started,
|
|
|
|
|
+ Stopped,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ /// 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;
|
|
|
|
|
+
|
|
|
|
|
+ /// 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::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
|
void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
|
|
- LOG_WARNING(Service, "(STUBBED) called");
|
|
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
IPC::RequestParser rp{ctx};
|
|
IPC::RequestParser rp{ctx};
|
|
|
|
|
|
|
|
auto& buffer = ctx.BufferDescriptorB()[0];
|
|
auto& buffer = ctx.BufferDescriptorB()[0];
|
|
@@ -50,16 +178,26 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
|
void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
|
|
- LOG_WARNING(Service, "(STUBBED) called");
|
|
|
|
|
|
|
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
|
|
|
|
|
+
|
|
|
|
|
+ if (!audio_out_interface) {
|
|
|
|
|
+ audio_out_interface = std::make_shared<IAudioOut>();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- IPC::RequestBuilder rb{ctx, 6};
|
|
|
|
|
|
|
+ auto sessions = Kernel::ServerSession::CreateSessionPair(audio_out_interface->GetServiceName());
|
|
|
|
|
+ auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
|
|
|
|
|
+ auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
|
|
|
|
|
+ audio_out_interface->ClientConnected(server);
|
|
|
|
|
+ LOG_DEBUG(Service, "called, initialized IAudioOut -> session=%u", client->GetObjectId());
|
|
|
|
|
+ IPC::RequestBuilder rb{ctx, 6, 0, 1};
|
|
|
|
|
|
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(RESULT_SUCCESS);
|
|
|
- rb.Push<u32>(48000); // Sample Rate
|
|
|
|
|
- rb.Push<u32>(2); // Channels
|
|
|
|
|
- rb.Push<u32>(2); // PCM Format (INT16)
|
|
|
|
|
- rb.Push<u32>(0); // Unknown
|
|
|
|
|
- rb.PushIpcInterface<Audio::IAudioOut>();
|
|
|
|
|
|
|
+ rb.Push<u32>(sample_rate);
|
|
|
|
|
+ rb.Push<u32>(audio_channels);
|
|
|
|
|
+ rb.Push<u32>(static_cast<u32>(PcmFormat::Int16));
|
|
|
|
|
+ // this field is unknown
|
|
|
|
|
+ rb.Push<u32>(0);
|
|
|
|
|
+ rb.PushMoveObjects(std::move(client));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
AudOutU::AudOutU() : ServiceFramework("audout:u") {
|
|
AudOutU::AudOutU() : ServiceFramework("audout:u") {
|