audout: Implement IAudioOut interface with AudioCore.
This commit is contained in:
		| @@ -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,22 @@ | ||||
|  | ||||
| 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) | ||||
|         : ServiceFramework("IAudioOut"), audio_params(audio_params), | ||||
|           audio_core(Core::System::GetInstance().AudioCore()) { | ||||
|  | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, | ||||
|             {1, &IAudioOut::StartAudioOut, "StartAudioOut"}, | ||||
| @@ -32,66 +36,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"); | ||||
|         buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); | ||||
|  | ||||
|         // 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() { | ||||
|         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"); | ||||
|  | ||||
|         // Start audio | ||||
|         audio_out_state = AudioState::Started; | ||||
|         if (stream->IsPlaying()) { | ||||
|             IPC::ResponseBuilder rb{ctx, 2}; | ||||
|             rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         audio_core.StartStream(stream); | ||||
|  | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|     } | ||||
|  | ||||
|     void StopAudioOut(Kernel::HLERequestContext& ctx) { | ||||
|         LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||||
|         LOG_DEBUG(Service_Audio, "called"); | ||||
|  | ||||
|         // Stop audio | ||||
|         audio_out_state = AudioState::Stopped; | ||||
|  | ||||
|         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 +102,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"); | ||||
|         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 | ||||
|         // 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(); | ||||
|         } | ||||
|  | ||||
|         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; | ||||
|         } | ||||
|  | ||||
|         buffer_event->Signal(); | ||||
|     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)); | ||||
|     } | ||||
|  | ||||
|     enum class AudioState : u32 { | ||||
|         Started, | ||||
|         Stopped, | ||||
|     }; | ||||
|     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())); | ||||
|     } | ||||
|  | ||||
|     /// 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; | ||||
|     AudioCore::AudioOut& audio_core; | ||||
|     AudioCore::StreamPtr stream; | ||||
|  | ||||
|     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)); | ||||
|  | ||||
|     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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,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> { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user