audout: Implement IAudioOut interface with AudioCore.

This commit is contained in:
bunnei 2018-07-26 22:35:23 -04:00
parent 2a742229ee
commit f1c519f2cb
2 changed files with 115 additions and 94 deletions

View File

@ -5,8 +5,7 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core_timing.h" #include "core/core.h"
#include "core/core_timing_util.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
@ -14,17 +13,22 @@
namespace Service::Audio { namespace Service::Audio {
/// Switch sample rate frequency namespace ErrCodes {
constexpr u32 sample_rate{48000}; enum {
/// TODO(st4rk): dynamic number of channels, as I think Switch has support ErrorUnknown = 2,
/// to more audio channels (probably when Docked I guess) BufferCountExceeded = 8,
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)};
constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
constexpr int DefaultSampleRate{48000};
class IAudioOut final : public ServiceFramework<IAudioOut> { class IAudioOut final : public ServiceFramework<IAudioOut> {
public: public:
IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) { IAudioOut(AudoutParams audio_params)
: ServiceFramework("IAudioOut"), audio_params(audio_params),
audio_core(Core::System::GetInstance().AudioCore()) {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
{1, &IAudioOut::StartAudioOut, "StartAudioOut"}, {1, &IAudioOut::StartAudioOut, "StartAudioOut"},
@ -32,66 +36,65 @@ public:
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"}, {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
{6, nullptr, "ContainsAudioOutBuffer"}, {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
{9, nullptr, "GetAudioOutBufferCount"}, {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"}, {10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"}, {11, nullptr, "FlushAudioOutBuffers"},
}; };
RegisterHandlers(functions); RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released // This is the event handle used to check if the audio buffer was released
buffer_event = buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
// Register event callback to update the Audio Buffer stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count,
audio_event = CoreTiming::RegisterEvent( [=]() { buffer_event->Signal(); });
"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() {
CoreTiming::UnscheduleEvent(audio_event, 0);
} }
private: 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) { void GetAudioOutState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called"); LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS); 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) { void StartAudioOut(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "called");
// Start audio if (stream->IsPlaying()) {
audio_out_state = AudioState::Started; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown));
return;
}
audio_core.StartStream(stream);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void StopAudioOut(Kernel::HLERequestContext& ctx) { void StopAudioOut(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "called");
// Stop audio audio_core.StopStream(stream);
audio_out_state = AudioState::Stopped;
queue_keys.clear();
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
@ -99,101 +102,107 @@ private:
} }
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const u64 key{rp.Pop<u64>()}; const auto& input_buffer{ctx.ReadBuffer()};
queue_keys.insert(queue_keys.begin(), key); 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}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); 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)};
// TODO(st4rk): This is how libtransistor currently implements the std::vector<u64> tags{released_buffers};
// GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address tags.resize(max_count);
// is used to know which buffer should be filled with data and send again to the service ctx.WriteBuffer(tags);
// 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();
}
ctx.WriteBuffer(&key, sizeof(u64));
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
// TODO(st4rk): This might be the total of released buffers, needs to be verified on rb.Push<u32>(static_cast<u32>(released_buffers.size()));
// hardware
rb.Push<u32>(static_cast<u32>(queue_keys.size()));
} }
void UpdateAudioBuffersCallback() { void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
if (audio_out_state != AudioState::Started) { LOG_DEBUG(Service_Audio, "called");
return; IPC::RequestParser rp{ctx};
const u64 tag{rp.Pop<u64>()};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(stream->ContainsBuffer(tag));
} }
if (queue_keys.empty()) { void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
return; LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(stream->GetQueueSize()));
} }
buffer_event->Signal(); AudioCore::AudioOut& audio_core;
} AudioCore::StreamPtr stream;
enum class AudioState : u32 { AudoutParams audio_params{};
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 /// This is the evend handle used to check if the audio buffer was released
Kernel::SharedPtr<Kernel::Event> buffer_event; 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) { void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "called");
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
constexpr std::array<char, 15> audio_interface{{"AudioInterface"}}; ctx.WriteBuffer(DefaultDevice);
ctx.WriteBuffer(audio_interface);
IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
// TODO(st4rk): We're currently returning only one audio interface (stringlist size). However, rb.Push<u32>(1); // Amount of audio devices
// it's highly possible to have more than one interface (despite that libtransistor requires
// only one).
rb.Push<u32>(1);
} }
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called"); LOG_DEBUG(Service_Audio, "called");
if (!audio_out_interface) { ctx.WriteBuffer(DefaultDevice);
audio_out_interface = std::make_shared<IAudioOut>(); 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));
IPC::ResponseBuilder rb{ctx, 6, 0, 1}; IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u32>(sample_rate); rb.Push<u32>(DefaultSampleRate);
rb.Push<u32>(audio_channels); rb.Push<u32>(params.channel_count);
rb.Push<u32>(static_cast<u32>(PcmFormat::Int16)); 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); rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface);
} }

View File

@ -12,6 +12,18 @@ class HLERequestContext;
namespace Service::Audio { 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 IAudioOut;
class AudOutU final : public ServiceFramework<AudOutU> { class AudOutU final : public ServiceFramework<AudOutU> {