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