diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/audio/hwopus.cpp | 176 |
1 files changed, 107 insertions, 69 deletions
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 11eba4a12..377e12cfa 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -9,43 +9,32 @@ | |||
| 9 | 9 | ||
| 10 | #include <opus.h> | 10 | #include <opus.h> |
| 11 | 11 | ||
| 12 | #include "common/common_funcs.h" | 12 | #include "common/assert.h" |
| 13 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 14 | #include "core/hle/ipc_helpers.h" | 14 | #include "core/hle/ipc_helpers.h" |
| 15 | #include "core/hle/kernel/hle_ipc.h" | 15 | #include "core/hle/kernel/hle_ipc.h" |
| 16 | #include "core/hle/service/audio/hwopus.h" | 16 | #include "core/hle/service/audio/hwopus.h" |
| 17 | 17 | ||
| 18 | namespace Service::Audio { | 18 | namespace Service::Audio { |
| 19 | 19 | namespace { | |
| 20 | struct OpusDeleter { | 20 | struct OpusDeleter { |
| 21 | void operator()(void* ptr) const { | 21 | void operator()(void* ptr) const { |
| 22 | operator delete(ptr); | 22 | operator delete(ptr); |
| 23 | } | 23 | } |
| 24 | }; | 24 | }; |
| 25 | 25 | ||
| 26 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | 26 | using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>; |
| 27 | public: | ||
| 28 | IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate, | ||
| 29 | u32 channel_count) | ||
| 30 | : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)), | ||
| 31 | sample_rate(sample_rate), channel_count(channel_count) { | ||
| 32 | // clang-format off | ||
| 33 | static const FunctionInfo functions[] = { | ||
| 34 | {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 35 | {1, nullptr, "SetContext"}, | ||
| 36 | {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, | ||
| 37 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 38 | {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 39 | {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 40 | {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 41 | {7, nullptr, "DecodeInterleavedForMultiStream"}, | ||
| 42 | }; | ||
| 43 | // clang-format on | ||
| 44 | 27 | ||
| 45 | RegisterHandlers(functions); | 28 | struct OpusPacketHeader { |
| 46 | } | 29 | // Packet size in bytes. |
| 30 | u32_be size; | ||
| 31 | // Indicates the final range of the codec's entropy coder. | ||
| 32 | u32_be final_range; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 47 | 35 | ||
| 48 | private: | 36 | class OpusDecoderStateBase { |
| 37 | public: | ||
| 49 | /// Describes extra behavior that may be asked of the decoding context. | 38 | /// Describes extra behavior that may be asked of the decoding context. |
| 50 | enum class ExtraBehavior { | 39 | enum class ExtraBehavior { |
| 51 | /// No extra behavior. | 40 | /// No extra behavior. |
| @@ -55,30 +44,36 @@ private: | |||
| 55 | ResetContext, | 44 | ResetContext, |
| 56 | }; | 45 | }; |
| 57 | 46 | ||
| 58 | void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { | 47 | enum class PerfTime { |
| 59 | LOG_DEBUG(Audio, "called"); | 48 | Disabled, |
| 60 | 49 | Enabled, | |
| 61 | DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None); | 50 | }; |
| 62 | } | ||
| 63 | |||
| 64 | void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { | ||
| 65 | LOG_DEBUG(Audio, "called"); | ||
| 66 | 51 | ||
| 67 | u64 performance = 0; | 52 | virtual ~OpusDecoderStateBase() = default; |
| 68 | DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None); | ||
| 69 | } | ||
| 70 | 53 | ||
| 71 | void DecodeInterleaved(Kernel::HLERequestContext& ctx) { | 54 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to |
| 72 | LOG_DEBUG(Audio, "called"); | 55 | // perform the decoding, as well as any relevant extra behavior. |
| 73 | 56 | virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, | |
| 74 | IPC::RequestParser rp{ctx}; | 57 | ExtraBehavior extra_behavior) = 0; |
| 75 | const auto extra_behavior = | 58 | }; |
| 76 | rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None; | ||
| 77 | 59 | ||
| 78 | u64 performance = 0; | 60 | // Represents the decoder state for a non-multistream decoder. |
| 79 | DecodeInterleavedHelper(ctx, &performance, extra_behavior); | 61 | class OpusDecoderState final : public OpusDecoderStateBase { |
| 62 | public: | ||
| 63 | explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count) | ||
| 64 | : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {} | ||
| 65 | |||
| 66 | void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, | ||
| 67 | ExtraBehavior extra_behavior) override { | ||
| 68 | if (perf_time == PerfTime::Disabled) { | ||
| 69 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); | ||
| 70 | } else { | ||
| 71 | u64 performance = 0; | ||
| 72 | DecodeInterleavedHelper(ctx, &performance, extra_behavior); | ||
| 73 | } | ||
| 80 | } | 74 | } |
| 81 | 75 | ||
| 76 | private: | ||
| 82 | void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance, | 77 | void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance, |
| 83 | ExtraBehavior extra_behavior) { | 78 | ExtraBehavior extra_behavior) { |
| 84 | u32 consumed = 0; | 79 | u32 consumed = 0; |
| @@ -89,8 +84,7 @@ private: | |||
| 89 | ResetDecoderContext(); | 84 | ResetDecoderContext(); |
| 90 | } | 85 | } |
| 91 | 86 | ||
| 92 | if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples, | 87 | if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { |
| 93 | performance)) { | ||
| 94 | LOG_ERROR(Audio, "Failed to decode opus data"); | 88 | LOG_ERROR(Audio, "Failed to decode opus data"); |
| 95 | IPC::ResponseBuilder rb{ctx, 2}; | 89 | IPC::ResponseBuilder rb{ctx, 2}; |
| 96 | // TODO(ogniK): Use correct error code | 90 | // TODO(ogniK): Use correct error code |
| @@ -109,27 +103,27 @@ private: | |||
| 109 | ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); | 103 | ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); |
| 110 | } | 104 | } |
| 111 | 105 | ||
| 112 | bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, | 106 | bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input, |
| 113 | std::vector<opus_int16>& output, u64* out_performance_time) { | 107 | std::vector<opus_int16>& output, u64* out_performance_time) const { |
| 114 | const auto start_time = std::chrono::high_resolution_clock::now(); | 108 | const auto start_time = std::chrono::high_resolution_clock::now(); |
| 115 | const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); | 109 | const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); |
| 116 | if (sizeof(OpusHeader) > input.size()) { | 110 | if (sizeof(OpusPacketHeader) > input.size()) { |
| 117 | LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", | 111 | LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", |
| 118 | sizeof(OpusHeader), input.size()); | 112 | sizeof(OpusPacketHeader), input.size()); |
| 119 | return false; | 113 | return false; |
| 120 | } | 114 | } |
| 121 | 115 | ||
| 122 | OpusHeader hdr{}; | 116 | OpusPacketHeader hdr{}; |
| 123 | std::memcpy(&hdr, input.data(), sizeof(OpusHeader)); | 117 | std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); |
| 124 | if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) { | 118 | if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { |
| 125 | LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", | 119 | LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", |
| 126 | sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size()); | 120 | sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); |
| 127 | return false; | 121 | return false; |
| 128 | } | 122 | } |
| 129 | 123 | ||
| 130 | const auto frame = input.data() + sizeof(OpusHeader); | 124 | const auto frame = input.data() + sizeof(OpusPacketHeader); |
| 131 | const auto decoded_sample_count = opus_packet_get_nb_samples( | 125 | const auto decoded_sample_count = opus_packet_get_nb_samples( |
| 132 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)), | 126 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), |
| 133 | static_cast<opus_int32>(sample_rate)); | 127 | static_cast<opus_int32>(sample_rate)); |
| 134 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { | 128 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { |
| 135 | LOG_ERROR( | 129 | LOG_ERROR( |
| @@ -141,18 +135,18 @@ private: | |||
| 141 | 135 | ||
| 142 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); | 136 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); |
| 143 | const auto out_sample_count = | 137 | const auto out_sample_count = |
| 144 | opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0); | 138 | opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); |
| 145 | if (out_sample_count < 0) { | 139 | if (out_sample_count < 0) { |
| 146 | LOG_ERROR(Audio, | 140 | LOG_ERROR(Audio, |
| 147 | "Incorrect sample count received from opus_decode, " | 141 | "Incorrect sample count received from opus_decode, " |
| 148 | "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", | 142 | "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", |
| 149 | out_sample_count, frame_size, static_cast<u32>(hdr.sz)); | 143 | out_sample_count, frame_size, static_cast<u32>(hdr.size)); |
| 150 | return false; | 144 | return false; |
| 151 | } | 145 | } |
| 152 | 146 | ||
| 153 | const auto end_time = std::chrono::high_resolution_clock::now() - start_time; | 147 | const auto end_time = std::chrono::high_resolution_clock::now() - start_time; |
| 154 | sample_count = out_sample_count; | 148 | sample_count = out_sample_count; |
| 155 | consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); | 149 | consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); |
| 156 | if (out_performance_time != nullptr) { | 150 | if (out_performance_time != nullptr) { |
| 157 | *out_performance_time = | 151 | *out_performance_time = |
| 158 | std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); | 152 | std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); |
| @@ -167,21 +161,66 @@ private: | |||
| 167 | opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE); | 161 | opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE); |
| 168 | } | 162 | } |
| 169 | 163 | ||
| 170 | struct OpusHeader { | 164 | OpusDecoderPtr decoder; |
| 171 | u32_be sz; // Needs to be BE for some odd reason | ||
| 172 | INSERT_PADDING_WORDS(1); | ||
| 173 | }; | ||
| 174 | static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 175 | |||
| 176 | std::unique_ptr<OpusDecoder, OpusDeleter> decoder; | ||
| 177 | u32 sample_rate; | 165 | u32 sample_rate; |
| 178 | u32 channel_count; | 166 | u32 channel_count; |
| 179 | }; | 167 | }; |
| 180 | 168 | ||
| 181 | static std::size_t WorkerBufferSize(u32 channel_count) { | 169 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { |
| 170 | public: | ||
| 171 | explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state) | ||
| 172 | : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { | ||
| 173 | // clang-format off | ||
| 174 | static const FunctionInfo functions[] = { | ||
| 175 | {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 176 | {1, nullptr, "SetContext"}, | ||
| 177 | {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, | ||
| 178 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 179 | {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 180 | {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 181 | {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 182 | {7, nullptr, "DecodeInterleavedForMultiStream"}, | ||
| 183 | }; | ||
| 184 | // clang-format on | ||
| 185 | |||
| 186 | RegisterHandlers(functions); | ||
| 187 | } | ||
| 188 | |||
| 189 | private: | ||
| 190 | void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { | ||
| 191 | LOG_DEBUG(Audio, "called"); | ||
| 192 | |||
| 193 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled, | ||
| 194 | OpusDecoderStateBase::ExtraBehavior::None); | ||
| 195 | } | ||
| 196 | |||
| 197 | void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { | ||
| 198 | LOG_DEBUG(Audio, "called"); | ||
| 199 | |||
| 200 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, | ||
| 201 | OpusDecoderStateBase::ExtraBehavior::None); | ||
| 202 | } | ||
| 203 | |||
| 204 | void DecodeInterleaved(Kernel::HLERequestContext& ctx) { | ||
| 205 | LOG_DEBUG(Audio, "called"); | ||
| 206 | |||
| 207 | IPC::RequestParser rp{ctx}; | ||
| 208 | const auto extra_behavior = rp.Pop<bool>() | ||
| 209 | ? OpusDecoderStateBase::ExtraBehavior::ResetContext | ||
| 210 | : OpusDecoderStateBase::ExtraBehavior::None; | ||
| 211 | |||
| 212 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, | ||
| 213 | extra_behavior); | ||
| 214 | } | ||
| 215 | |||
| 216 | std::unique_ptr<OpusDecoderStateBase> decoder_state; | ||
| 217 | }; | ||
| 218 | |||
| 219 | std::size_t WorkerBufferSize(u32 channel_count) { | ||
| 182 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 220 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); |
| 183 | return opus_decoder_get_size(static_cast<int>(channel_count)); | 221 | return opus_decoder_get_size(static_cast<int>(channel_count)); |
| 184 | } | 222 | } |
| 223 | } // Anonymous namespace | ||
| 185 | 224 | ||
| 186 | void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { | 225 | void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { |
| 187 | IPC::RequestParser rp{ctx}; | 226 | IPC::RequestParser rp{ctx}; |
| @@ -220,8 +259,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { | |||
| 220 | const std::size_t worker_sz = WorkerBufferSize(channel_count); | 259 | const std::size_t worker_sz = WorkerBufferSize(channel_count); |
| 221 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); | 260 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); |
| 222 | 261 | ||
| 223 | std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ | 262 | OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))}; |
| 224 | static_cast<OpusDecoder*>(operator new(worker_sz))}; | ||
| 225 | if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { | 263 | if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { |
| 226 | LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); | 264 | LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); |
| 227 | IPC::ResponseBuilder rb{ctx, 2}; | 265 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -232,8 +270,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { | |||
| 232 | 270 | ||
| 233 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 271 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 234 | rb.Push(RESULT_SUCCESS); | 272 | rb.Push(RESULT_SUCCESS); |
| 235 | rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate, | 273 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( |
| 236 | channel_count); | 274 | std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count)); |
| 237 | } | 275 | } |
| 238 | 276 | ||
| 239 | HwOpus::HwOpus() : ServiceFramework("hwopus") { | 277 | HwOpus::HwOpus() : ServiceFramework("hwopus") { |