1 // Copyright 2018 yuzu emulator team
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <chrono>
6 #include <cstring>
7 #include <memory>
8 #include <vector>
9
10 #include <opus.h>
11 #include <opus_multistream.h>
12
13 #include "common/assert.h"
14 #include "common/logging/log.h"
15 #include "core/hle/ipc_helpers.h"
16 #include "core/hle/kernel/hle_ipc.h"
17 #include "core/hle/service/audio/hwopus.h"
18
19 namespace Service::Audio {
20 namespace {
21 struct OpusDeleter {
operator ()Service::Audio::__anonc365002e0111::OpusDeleter22 void operator()(OpusMSDecoder* ptr) const {
23 opus_multistream_decoder_destroy(ptr);
24 }
25 };
26
27 using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
28
29 struct OpusPacketHeader {
30 // Packet size in bytes.
31 u32_be size;
32 // Indicates the final range of the codec's entropy coder.
33 u32_be final_range;
34 };
35 static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
36
37 class OpusDecoderState {
38 public:
39 /// Describes extra behavior that may be asked of the decoding context.
40 enum class ExtraBehavior {
41 /// No extra behavior.
42 None,
43
44 /// Resets the decoder context back to a freshly initialized state.
45 ResetContext,
46 };
47
48 enum class PerfTime {
49 Disabled,
50 Enabled,
51 };
52
OpusDecoderState(OpusDecoderPtr decoder,u32 sample_rate,u32 channel_count)53 explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
54 : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
55
56 // Decodes interleaved Opus packets. Optionally allows reporting time taken to
57 // perform the decoding, as well as any relevant extra behavior.
DecodeInterleaved(Kernel::HLERequestContext & ctx,PerfTime perf_time,ExtraBehavior extra_behavior)58 void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
59 ExtraBehavior extra_behavior) {
60 if (perf_time == PerfTime::Disabled) {
61 DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
62 } else {
63 u64 performance = 0;
64 DecodeInterleavedHelper(ctx, &performance, extra_behavior);
65 }
66 }
67
68 private:
DecodeInterleavedHelper(Kernel::HLERequestContext & ctx,u64 * performance,ExtraBehavior extra_behavior)69 void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
70 ExtraBehavior extra_behavior) {
71 u32 consumed = 0;
72 u32 sample_count = 0;
73 std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
74
75 if (extra_behavior == ExtraBehavior::ResetContext) {
76 ResetDecoderContext();
77 }
78
79 if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
80 LOG_ERROR(Audio, "Failed to decode opus data");
81 IPC::ResponseBuilder rb{ctx, 2};
82 // TODO(ogniK): Use correct error code
83 rb.Push(RESULT_UNKNOWN);
84 return;
85 }
86
87 const u32 param_size = performance != nullptr ? 6 : 4;
88 IPC::ResponseBuilder rb{ctx, param_size};
89 rb.Push(RESULT_SUCCESS);
90 rb.Push<u32>(consumed);
91 rb.Push<u32>(sample_count);
92 if (performance) {
93 rb.Push<u64>(*performance);
94 }
95 ctx.WriteBuffer(samples);
96 }
97
DecodeOpusData(u32 & consumed,u32 & sample_count,const std::vector<u8> & input,std::vector<opus_int16> & output,u64 * out_performance_time) const98 bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
99 std::vector<opus_int16>& output, u64* out_performance_time) const {
100 const auto start_time = std::chrono::high_resolution_clock::now();
101 const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
102 if (sizeof(OpusPacketHeader) > input.size()) {
103 LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
104 sizeof(OpusPacketHeader), input.size());
105 return false;
106 }
107
108 OpusPacketHeader hdr{};
109 std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
110 if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
111 LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
112 sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
113 return false;
114 }
115
116 const auto frame = input.data() + sizeof(OpusPacketHeader);
117 const auto decoded_sample_count = opus_packet_get_nb_samples(
118 frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
119 static_cast<opus_int32>(sample_rate));
120 if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
121 LOG_ERROR(
122 Audio,
123 "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
124 decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
125 return false;
126 }
127
128 const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
129 const auto out_sample_count =
130 opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
131 if (out_sample_count < 0) {
132 LOG_ERROR(Audio,
133 "Incorrect sample count received from opus_decode, "
134 "output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
135 out_sample_count, frame_size, static_cast<u32>(hdr.size));
136 return false;
137 }
138
139 const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
140 sample_count = out_sample_count;
141 consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
142 if (out_performance_time != nullptr) {
143 *out_performance_time =
144 std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
145 }
146
147 return true;
148 }
149
ResetDecoderContext()150 void ResetDecoderContext() {
151 ASSERT(decoder != nullptr);
152
153 opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
154 }
155
156 OpusDecoderPtr decoder;
157 u32 sample_rate;
158 u32 channel_count;
159 };
160
161 class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
162 public:
IHardwareOpusDecoderManager(Core::System & system_,OpusDecoderState decoder_state)163 explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state)
164 : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
165 std::move(decoder_state)} {
166 // clang-format off
167 static const FunctionInfo functions[] = {
168 {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
169 {1, nullptr, "SetContext"},
170 {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
171 {3, nullptr, "SetContextForMultiStream"},
172 {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
173 {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
174 {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
175 {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
176 {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
177 {9, nullptr, "DecodeInterleavedForMultiStream"},
178 };
179 // clang-format on
180
181 RegisterHandlers(functions);
182 }
183
184 private:
DecodeInterleavedOld(Kernel::HLERequestContext & ctx)185 void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
186 LOG_DEBUG(Audio, "called");
187
188 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
189 OpusDecoderState::ExtraBehavior::None);
190 }
191
DecodeInterleavedWithPerfOld(Kernel::HLERequestContext & ctx)192 void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
193 LOG_DEBUG(Audio, "called");
194
195 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
196 OpusDecoderState::ExtraBehavior::None);
197 }
198
DecodeInterleaved(Kernel::HLERequestContext & ctx)199 void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
200 LOG_DEBUG(Audio, "called");
201
202 IPC::RequestParser rp{ctx};
203 const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
204 : OpusDecoderState::ExtraBehavior::None;
205
206 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
207 }
208
209 OpusDecoderState decoder_state;
210 };
211
WorkerBufferSize(u32 channel_count)212 std::size_t WorkerBufferSize(u32 channel_count) {
213 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
214 constexpr int num_streams = 1;
215 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
216 return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
217 }
218
219 // Creates the mapping table that maps the input channels to the particular
220 // output channels. In the stereo case, we map the left and right input channels
221 // to the left and right output channels respectively.
222 //
223 // However, in the monophonic case, we only map the one available channel
224 // to the sole output channel. We specify 255 for the would-be right channel
225 // as this is a special value defined by Opus to indicate to the decoder to
226 // ignore that channel.
CreateMappingTable(u32 channel_count)227 std::array<u8, 2> CreateMappingTable(u32 channel_count) {
228 if (channel_count == 2) {
229 return {{0, 1}};
230 }
231
232 return {{0, 255}};
233 }
234 } // Anonymous namespace
235
GetWorkBufferSize(Kernel::HLERequestContext & ctx)236 void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
237 IPC::RequestParser rp{ctx};
238 const auto sample_rate = rp.Pop<u32>();
239 const auto channel_count = rp.Pop<u32>();
240
241 LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
242
243 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
244 sample_rate == 12000 || sample_rate == 8000,
245 "Invalid sample rate");
246 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
247
248 const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
249 LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz);
250
251 IPC::ResponseBuilder rb{ctx, 3};
252 rb.Push(RESULT_SUCCESS);
253 rb.Push<u32>(worker_buffer_sz);
254 }
255
OpenOpusDecoder(Kernel::HLERequestContext & ctx)256 void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
257 IPC::RequestParser rp{ctx};
258 const auto sample_rate = rp.Pop<u32>();
259 const auto channel_count = rp.Pop<u32>();
260 const auto buffer_sz = rp.Pop<u32>();
261
262 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
263 channel_count, buffer_sz);
264
265 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
266 sample_rate == 12000 || sample_rate == 8000,
267 "Invalid sample rate");
268 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
269
270 const std::size_t worker_sz = WorkerBufferSize(channel_count);
271 ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
272
273 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
274 const auto mapping_table = CreateMappingTable(channel_count);
275
276 int error = 0;
277 OpusDecoderPtr decoder{
278 opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
279 num_stereo_streams, mapping_table.data(), &error)};
280 if (error != OPUS_OK || decoder == nullptr) {
281 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
282 IPC::ResponseBuilder rb{ctx, 2};
283 // TODO(ogniK): Use correct error code
284 rb.Push(RESULT_UNKNOWN);
285 return;
286 }
287
288 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
289 rb.Push(RESULT_SUCCESS);
290 rb.PushIpcInterface<IHardwareOpusDecoderManager>(
291 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
292 }
293
HwOpus(Core::System & system_)294 HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
295 static const FunctionInfo functions[] = {
296 {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
297 {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
298 {2, nullptr, "OpenOpusDecoderForMultiStream"},
299 {3, nullptr, "GetWorkBufferSizeForMultiStream"},
300 };
301 RegisterHandlers(functions);
302 }
303
304 HwOpus::~HwOpus() = default;
305
306 } // namespace Service::Audio
307