diff options
| -rw-r--r-- | src/core/hle/service/audio/hwopus.cpp | 82 |
1 files changed, 48 insertions, 34 deletions
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 377e12cfa..cb4a1160d 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | 9 | ||
| 10 | #include <opus.h> | 10 | #include <opus.h> |
| 11 | #include <opus_multistream.h> | ||
| 11 | 12 | ||
| 12 | #include "common/assert.h" | 13 | #include "common/assert.h" |
| 13 | #include "common/logging/log.h" | 14 | #include "common/logging/log.h" |
| @@ -18,12 +19,12 @@ | |||
| 18 | namespace Service::Audio { | 19 | namespace Service::Audio { |
| 19 | namespace { | 20 | namespace { |
| 20 | struct OpusDeleter { | 21 | struct OpusDeleter { |
| 21 | void operator()(void* ptr) const { | 22 | void operator()(OpusMSDecoder* ptr) const { |
| 22 | operator delete(ptr); | 23 | opus_multistream_decoder_destroy(ptr); |
| 23 | } | 24 | } |
| 24 | }; | 25 | }; |
| 25 | 26 | ||
| 26 | using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>; | 27 | using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; |
| 27 | 28 | ||
| 28 | struct OpusPacketHeader { | 29 | struct OpusPacketHeader { |
| 29 | // Packet size in bytes. | 30 | // Packet size in bytes. |
| @@ -33,7 +34,7 @@ struct OpusPacketHeader { | |||
| 33 | }; | 34 | }; |
| 34 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); | 35 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); |
| 35 | 36 | ||
| 36 | class OpusDecoderStateBase { | 37 | class OpusDecoderState { |
| 37 | public: | 38 | public: |
| 38 | /// Describes extra behavior that may be asked of the decoding context. | 39 | /// Describes extra behavior that may be asked of the decoding context. |
| 39 | enum class ExtraBehavior { | 40 | enum class ExtraBehavior { |
| @@ -49,22 +50,13 @@ public: | |||
| 49 | Enabled, | 50 | Enabled, |
| 50 | }; | 51 | }; |
| 51 | 52 | ||
| 52 | virtual ~OpusDecoderStateBase() = default; | ||
| 53 | |||
| 54 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to | ||
| 55 | // perform the decoding, as well as any relevant extra behavior. | ||
| 56 | virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, | ||
| 57 | ExtraBehavior extra_behavior) = 0; | ||
| 58 | }; | ||
| 59 | |||
| 60 | // Represents the decoder state for a non-multistream decoder. | ||
| 61 | class OpusDecoderState final : public OpusDecoderStateBase { | ||
| 62 | public: | ||
| 63 | explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count) | 53 | explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count) |
| 64 | : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {} | 54 | : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {} |
| 65 | 55 | ||
| 56 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to | ||
| 57 | // perform the decoding, as well as any relevant extra behavior. | ||
| 66 | void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, | 58 | void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, |
| 67 | ExtraBehavior extra_behavior) override { | 59 | ExtraBehavior extra_behavior) { |
| 68 | if (perf_time == PerfTime::Disabled) { | 60 | if (perf_time == PerfTime::Disabled) { |
| 69 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); | 61 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); |
| 70 | } else { | 62 | } else { |
| @@ -135,7 +127,7 @@ private: | |||
| 135 | 127 | ||
| 136 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); | 128 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); |
| 137 | const auto out_sample_count = | 129 | const auto out_sample_count = |
| 138 | opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); | 130 | opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); |
| 139 | if (out_sample_count < 0) { | 131 | if (out_sample_count < 0) { |
| 140 | LOG_ERROR(Audio, | 132 | LOG_ERROR(Audio, |
| 141 | "Incorrect sample count received from opus_decode, " | 133 | "Incorrect sample count received from opus_decode, " |
| @@ -158,7 +150,7 @@ private: | |||
| 158 | void ResetDecoderContext() { | 150 | void ResetDecoderContext() { |
| 159 | ASSERT(decoder != nullptr); | 151 | ASSERT(decoder != nullptr); |
| 160 | 152 | ||
| 161 | opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE); | 153 | opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); |
| 162 | } | 154 | } |
| 163 | 155 | ||
| 164 | OpusDecoderPtr decoder; | 156 | OpusDecoderPtr decoder; |
| @@ -168,7 +160,7 @@ private: | |||
| 168 | 160 | ||
| 169 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | 161 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { |
| 170 | public: | 162 | public: |
| 171 | explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state) | 163 | explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state) |
| 172 | : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { | 164 | : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { |
| 173 | // clang-format off | 165 | // clang-format off |
| 174 | static const FunctionInfo functions[] = { | 166 | static const FunctionInfo functions[] = { |
| @@ -190,35 +182,51 @@ private: | |||
| 190 | void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { | 182 | void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { |
| 191 | LOG_DEBUG(Audio, "called"); | 183 | LOG_DEBUG(Audio, "called"); |
| 192 | 184 | ||
| 193 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled, | 185 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, |
| 194 | OpusDecoderStateBase::ExtraBehavior::None); | 186 | OpusDecoderState::ExtraBehavior::None); |
| 195 | } | 187 | } |
| 196 | 188 | ||
| 197 | void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { | 189 | void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { |
| 198 | LOG_DEBUG(Audio, "called"); | 190 | LOG_DEBUG(Audio, "called"); |
| 199 | 191 | ||
| 200 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, | 192 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, |
| 201 | OpusDecoderStateBase::ExtraBehavior::None); | 193 | OpusDecoderState::ExtraBehavior::None); |
| 202 | } | 194 | } |
| 203 | 195 | ||
| 204 | void DecodeInterleaved(Kernel::HLERequestContext& ctx) { | 196 | void DecodeInterleaved(Kernel::HLERequestContext& ctx) { |
| 205 | LOG_DEBUG(Audio, "called"); | 197 | LOG_DEBUG(Audio, "called"); |
| 206 | 198 | ||
| 207 | IPC::RequestParser rp{ctx}; | 199 | IPC::RequestParser rp{ctx}; |
| 208 | const auto extra_behavior = rp.Pop<bool>() | 200 | const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext |
| 209 | ? OpusDecoderStateBase::ExtraBehavior::ResetContext | 201 | : OpusDecoderState::ExtraBehavior::None; |
| 210 | : OpusDecoderStateBase::ExtraBehavior::None; | ||
| 211 | 202 | ||
| 212 | decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, | 203 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); |
| 213 | extra_behavior); | ||
| 214 | } | 204 | } |
| 215 | 205 | ||
| 216 | std::unique_ptr<OpusDecoderStateBase> decoder_state; | 206 | OpusDecoderState decoder_state; |
| 217 | }; | 207 | }; |
| 218 | 208 | ||
| 219 | std::size_t WorkerBufferSize(u32 channel_count) { | 209 | std::size_t WorkerBufferSize(u32 channel_count) { |
| 220 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 210 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); |
| 221 | return opus_decoder_get_size(static_cast<int>(channel_count)); | 211 | constexpr int num_streams = 1; |
| 212 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 213 | return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); | ||
| 214 | } | ||
| 215 | |||
| 216 | // Creates the mapping table that maps the input channels to the particular | ||
| 217 | // output channels. In the stereo case, we map the left and right input channels | ||
| 218 | // to the left and right output channels respectively. | ||
| 219 | // | ||
| 220 | // However, in the monophonic case, we only map the one available channel | ||
| 221 | // to the sole output channel. We specify 255 for the would-be right channel | ||
| 222 | // as this is a special value defined by Opus to indicate to the decoder to | ||
| 223 | // ignore that channel. | ||
| 224 | std::array<u8, 2> CreateMappingTable(u32 channel_count) { | ||
| 225 | if (channel_count == 2) { | ||
| 226 | return {{0, 1}}; | ||
| 227 | } | ||
| 228 | |||
| 229 | return {{0, 255}}; | ||
| 222 | } | 230 | } |
| 223 | } // Anonymous namespace | 231 | } // Anonymous namespace |
| 224 | 232 | ||
| @@ -259,9 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { | |||
| 259 | const std::size_t worker_sz = WorkerBufferSize(channel_count); | 267 | const std::size_t worker_sz = WorkerBufferSize(channel_count); |
| 260 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); | 268 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); |
| 261 | 269 | ||
| 262 | OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))}; | 270 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; |
| 263 | if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { | 271 | const auto mapping_table = CreateMappingTable(channel_count); |
| 264 | LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); | 272 | |
| 273 | int error = 0; | ||
| 274 | OpusDecoderPtr decoder{ | ||
| 275 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | ||
| 276 | num_stereo_streams, mapping_table.data(), &error)}; | ||
| 277 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 278 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 265 | IPC::ResponseBuilder rb{ctx, 2}; | 279 | IPC::ResponseBuilder rb{ctx, 2}; |
| 266 | // TODO(ogniK): Use correct error code | 280 | // TODO(ogniK): Use correct error code |
| 267 | rb.Push(ResultCode(-1)); | 281 | rb.Push(ResultCode(-1)); |
| @@ -271,7 +285,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { | |||
| 271 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 285 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 272 | rb.Push(RESULT_SUCCESS); | 286 | rb.Push(RESULT_SUCCESS); |
| 273 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 287 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( |
| 274 | std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count)); | 288 | OpusDecoderState{std::move(decoder), sample_rate, channel_count}); |
| 275 | } | 289 | } |
| 276 | 290 | ||
| 277 | HwOpus::HwOpus() : ServiceFramework("hwopus") { | 291 | HwOpus::HwOpus() : ServiceFramework("hwopus") { |