diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/hle/service/audio/hwopus.cpp | 135 | ||||
| -rw-r--r-- | src/core/hle/service/audio/hwopus.h | 1 |
3 files changed, 132 insertions, 6 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f05e02cfb..3386c2231 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -318,7 +318,7 @@ add_library(core STATIC | |||
| 318 | create_target_directory_groups(core) | 318 | create_target_directory_groups(core) |
| 319 | 319 | ||
| 320 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | 320 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) |
| 321 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn) | 321 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn) |
| 322 | 322 | ||
| 323 | if (ARCHITECTURE_x86_64) | 323 | if (ARCHITECTURE_x86_64) |
| 324 | target_sources(core PRIVATE | 324 | target_sources(core PRIVATE |
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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <cstring> | ||
| 6 | #include <opus.h> | ||
| 5 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 6 | #include "core/hle/ipc_helpers.h" | 8 | #include "core/hle/ipc_helpers.h" |
| 7 | #include "core/hle/kernel/hle_ipc.h" | 9 | #include "core/hle/kernel/hle_ipc.h" |
| @@ -9,19 +11,142 @@ | |||
| 9 | 11 | ||
| 10 | namespace Service::Audio { | 12 | namespace Service::Audio { |
| 11 | 13 | ||
| 14 | struct OpusDeleter { | ||
| 15 | void operator()(void* ptr) const { | ||
| 16 | operator delete(ptr); | ||
| 17 | } | ||
| 18 | }; | ||
| 19 | |||
| 20 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | ||
| 21 | public: | ||
| 22 | IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate, | ||
| 23 | u32 channel_count) | ||
| 24 | : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)), | ||
| 25 | sample_rate(sample_rate), channel_count(channel_count) { | ||
| 26 | static const FunctionInfo functions[] = { | ||
| 27 | {0, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 28 | {1, nullptr, "SetContext"}, | ||
| 29 | {2, nullptr, "DecodeInterleavedForMultiStream"}, | ||
| 30 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 31 | {4, nullptr, "Unknown4"}, | ||
| 32 | {5, nullptr, "Unknown5"}, | ||
| 33 | {6, nullptr, "Unknown6"}, | ||
| 34 | {7, nullptr, "Unknown7"}, | ||
| 35 | }; | ||
| 36 | RegisterHandlers(functions); | ||
| 37 | } | ||
| 38 | |||
| 39 | private: | ||
| 40 | void DecodeInterleaved(Kernel::HLERequestContext& ctx) { | ||
| 41 | u32 consumed = 0; | ||
| 42 | u32 sample_count = 0; | ||
| 43 | std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16)); | ||
| 44 | if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) { | ||
| 45 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 46 | // TODO(ogniK): Use correct error code | ||
| 47 | rb.Push(ResultCode(-1)); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 51 | rb.Push(RESULT_SUCCESS); | ||
| 52 | rb.Push<u32>(consumed); | ||
| 53 | rb.Push<u32>(sample_count); | ||
| 54 | ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); | ||
| 55 | } | ||
| 56 | |||
| 57 | bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, | ||
| 58 | std::vector<opus_int16>& output) { | ||
| 59 | size_t raw_output_sz = output.size() * sizeof(opus_int16); | ||
| 60 | if (sizeof(OpusHeader) > input.size()) | ||
| 61 | return false; | ||
| 62 | OpusHeader hdr{}; | ||
| 63 | std::memcpy(&hdr, input.data(), sizeof(OpusHeader)); | ||
| 64 | if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) { | ||
| 65 | return false; | ||
| 66 | } | ||
| 67 | auto frame = input.data() + sizeof(OpusHeader); | ||
| 68 | auto decoded_sample_count = opus_packet_get_nb_samples( | ||
| 69 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)), | ||
| 70 | static_cast<opus_int32>(sample_rate)); | ||
| 71 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) | ||
| 72 | return false; | ||
| 73 | auto out_sample_count = | ||
| 74 | opus_decode(decoder.get(), frame, hdr.sz, output.data(), | ||
| 75 | (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0); | ||
| 76 | if (out_sample_count < 0) | ||
| 77 | return false; | ||
| 78 | sample_count = out_sample_count; | ||
| 79 | consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); | ||
| 80 | return true; | ||
| 81 | } | ||
| 82 | |||
| 83 | struct OpusHeader { | ||
| 84 | u32_be sz; // Needs to be BE for some odd reason | ||
| 85 | INSERT_PADDING_WORDS(1); | ||
| 86 | }; | ||
| 87 | static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 88 | |||
| 89 | std::unique_ptr<OpusDecoder, OpusDeleter> decoder; | ||
| 90 | u32 sample_rate; | ||
| 91 | u32 channel_count; | ||
| 92 | }; | ||
| 93 | |||
| 94 | static size_t WorkerBufferSize(u32 channel_count) { | ||
| 95 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 96 | return opus_decoder_get_size(static_cast<int>(channel_count)); | ||
| 97 | } | ||
| 98 | |||
| 12 | void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { | 99 | void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { |
| 13 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 100 | IPC::RequestParser rp{ctx}; |
| 101 | auto sample_rate = rp.Pop<u32>(); | ||
| 102 | auto channel_count = rp.Pop<u32>(); | ||
| 103 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 104 | sample_rate == 12000 || sample_rate == 8000, | ||
| 105 | "Invalid sample rate"); | ||
| 106 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 107 | u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); | ||
| 108 | LOG_DEBUG(Audio, "called worker_buffer_sz={}", worker_buffer_sz); | ||
| 109 | |||
| 14 | IPC::ResponseBuilder rb{ctx, 3}; | 110 | IPC::ResponseBuilder rb{ctx, 3}; |
| 15 | rb.Push(RESULT_SUCCESS); | 111 | rb.Push(RESULT_SUCCESS); |
| 16 | rb.Push<u32>(0x4000); | 112 | rb.Push<u32>(worker_buffer_sz); |
| 113 | } | ||
| 114 | |||
| 115 | void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { | ||
| 116 | IPC::RequestParser rp{ctx}; | ||
| 117 | auto sample_rate = rp.Pop<u32>(); | ||
| 118 | auto channel_count = rp.Pop<u32>(); | ||
| 119 | auto buffer_sz = rp.Pop<u32>(); | ||
| 120 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, | ||
| 121 | channel_count, buffer_sz); | ||
| 122 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 123 | sample_rate == 12000 || sample_rate == 8000, | ||
| 124 | "Invalid sample rate"); | ||
| 125 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 126 | |||
| 127 | size_t worker_sz = WorkerBufferSize(channel_count); | ||
| 128 | ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large"); | ||
| 129 | std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ | ||
| 130 | static_cast<OpusDecoder*>(operator new(worker_sz))}; | ||
| 131 | if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) { | ||
| 132 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 133 | // TODO(ogniK): Use correct error code | ||
| 134 | rb.Push(ResultCode(-1)); | ||
| 135 | return; | ||
| 136 | } | ||
| 137 | |||
| 138 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 139 | rb.Push(RESULT_SUCCESS); | ||
| 140 | rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate, | ||
| 141 | channel_count); | ||
| 17 | } | 142 | } |
| 18 | 143 | ||
| 19 | HwOpus::HwOpus() : ServiceFramework("hwopus") { | 144 | HwOpus::HwOpus() : ServiceFramework("hwopus") { |
| 20 | static const FunctionInfo functions[] = { | 145 | static const FunctionInfo functions[] = { |
| 21 | {0, nullptr, "Initialize"}, | 146 | {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"}, |
| 22 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, | 147 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, |
| 23 | {2, nullptr, "InitializeMultiStream"}, | 148 | {2, nullptr, "OpenOpusDecoderForMultiStream"}, |
| 24 | {3, nullptr, "GetWorkBufferSizeMultiStream"}, | 149 | {3, nullptr, "GetWorkBufferSizeForMultiStream"}, |
| 25 | }; | 150 | }; |
| 26 | RegisterHandlers(functions); | 151 | RegisterHandlers(functions); |
| 27 | } | 152 | } |
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: | |||
| 14 | ~HwOpus() = default; | 14 | ~HwOpus() = default; |
| 15 | 15 | ||
| 16 | private: | 16 | private: |
| 17 | void OpenOpusDecoder(Kernel::HLERequestContext& ctx); | ||
| 17 | void GetWorkBufferSize(Kernel::HLERequestContext& ctx); | 18 | void GetWorkBufferSize(Kernel::HLERequestContext& ctx); |
| 18 | }; | 19 | }; |
| 19 | 20 | ||