hwopus decoder service implementation

This commit is contained in:
sspacelynx 2021-07-14 16:39:17 +02:00 committed by ◱ Mark
parent 3de4c3e32e
commit 06674b3d07
9 changed files with 268 additions and 2 deletions

View File

@ -161,6 +161,8 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/am/storage/IStorage.cpp
${source_DIR}/skyline/services/am/storage/IStorageAccessor.cpp
${source_DIR}/skyline/services/am/applet/ILibraryAppletAccessor.cpp
${source_DIR}/skyline/services/codec/IHardwareOpusDecoder.cpp
${source_DIR}/skyline/services/codec/IHardwareOpusDecoderManager.cpp
${source_DIR}/skyline/services/hid/IHidServer.cpp
${source_DIR}/skyline/services/hid/IAppletResource.cpp
${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp

View File

@ -121,6 +121,7 @@ namespace skyline {
namespace constant {
// Time
constexpr u64 NsInMicrosecond{1000}; //!< The amount of nanoseconds in a microsecond
constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second
constexpr u64 NsInMillisecond{1000000}; //!< The amount of nanoseconds in a millisecond
constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <services/timesrv/common.h>
#include <kernel/types/KProcess.h>
#include "IHardwareOpusDecoder.h"
namespace skyline::service::codec {
size_t CalculateOutBufferSize(i32 sampleRate, i32 channelCount, i32 frameSize) {
return util::AlignUp(frameSize * channelCount / (OpusFullbandSampleRate / sampleRate), 0x40);
}
IHardwareOpusDecoder::IHardwareOpusDecoder(const DeviceState &state, ServiceManager &manager, i32 sampleRate, i32 channelCount, u32 workBufferSize, KHandle workBufferHandle)
: BaseService(state, manager),
sampleRate(sampleRate),
channelCount(channelCount),
workBuffer(state.process->GetHandle<kernel::type::KTransferMemory>(workBufferHandle)),
decoderOutputBufferSize(CalculateOutBufferSize(sampleRate, channelCount, MaxFrameSizeNormal)) {
if (workBufferSize < decoderOutputBufferSize)
throw exception("Work Buffer doesn't have adequate space for Opus Decoder: 0x{:X} (Required: 0x{:X})", workBufferSize, decoderOutputBufferSize);
// We utilize the guest-supplied work buffer for allocating the OpusDecoder object into
decoderState = reinterpret_cast<OpusDecoder *>(workBuffer->host.ptr);
int result{opus_decoder_init(decoderState, sampleRate, channelCount)};
if (result != OPUS_OK)
throw OpusException(result);
}
Result IHardwareOpusDecoder::DecodeInterleavedOld(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return DecodeInterleavedImpl(request, response);
}
Result IHardwareOpusDecoder::DecodeInterleavedWithPerfOld(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return DecodeInterleavedImpl(request, response, true);
}
Result IHardwareOpusDecoder::DecodeInterleaved(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
bool reset{static_cast<bool>(request.Pop<u8>())};
if (reset)
ResetContext();
return DecodeInterleavedImpl(request, response, true);
}
void IHardwareOpusDecoder::ResetContext() {
opus_decoder_ctl(decoderState, OPUS_RESET_STATE);
}
Result IHardwareOpusDecoder::DecodeInterleavedImpl(ipc::IpcRequest &request, ipc::IpcResponse &response, bool writeDecodeTime) {
auto dataIn{request.inputBuf.at(0)};
auto dataOut{request.outputBuf.at(0).cast<opus_int16>()};
if (dataIn.size() <= sizeof(OpusDataHeader))
throw exception("Incorrect Opus data size: 0x{:X} (Should be > 0x{:X})", dataIn.size(), sizeof(OpusDataHeader));
u32 opusPacketSize{dataIn.as<OpusDataHeader>().GetPacketSize()};
i32 requiredInSize{static_cast<i32>(opusPacketSize + sizeof(OpusDataHeader))};
if (opusPacketSize > MaxInputBufferSize || dataIn.size() < requiredInSize)
throw exception("Opus packet size mismatch: 0x{:X} (Requested: 0x{:X})", dataIn.size() - sizeof(OpusDataHeader), opusPacketSize);
// Skip past the header in the input buffer to get the Opus packet
auto sampleDataIn = dataIn.subspan(sizeof(OpusDataHeader));
auto perfTimer{timesrv::TimeSpanType::FromNanoseconds(util::GetTimeNs())};
i32 decodedCount{opus_decode(decoderState, sampleDataIn.data(), opusPacketSize, dataOut.data(), decoderOutputBufferSize, false)};
perfTimer = timesrv::TimeSpanType::FromNanoseconds(util::GetTimeNs()) - perfTimer;
if (decodedCount < 0)
throw OpusException(decodedCount);
response.Push(requiredInSize); // Decoded data size is equal to opus packet size + header
response.Push(decodedCount);
if (writeDecodeTime)
response.Push<u64>(perfTimer.Microseconds());
return {};
}
}

View File

@ -0,0 +1,97 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <opus.h>
#include <common.h>
#include <services/base_service.h>
#include <kernel/types/KTransferMemory.h>
namespace skyline::service::codec {
/**
* @return The required output buffer size for decoding an Opus stream with the given parameters
*/
size_t CalculateOutBufferSize(i32 sampleRate, i32 channelCount, i32 frameSize);
static constexpr u32 OpusFullbandSampleRate{48000};
static constexpr u32 MaxFrameSizeNormal = OpusFullbandSampleRate * 0.040; //!< 40ms frame size limit for normal decoders
static constexpr u32 MaxFrameSizeEx = OpusFullbandSampleRate * 0.120; //!< 120ms frame size limit for ex decoders added in 12.0.0
static constexpr u32 MaxInputBufferSize{0x600}; //!< Maximum allocated size of the input buffer
/**
* @note The Switch has a HW Opus Decoder which this service would interface with, we emulate it using libopus with CPU-decoding
* @url https://switchbrew.org/wiki/Audio_services#IHardwareOpusDecoder
*/
class IHardwareOpusDecoder : public BaseService {
private:
std::shared_ptr<kernel::type::KTransferMemory> workBuffer;
OpusDecoder *decoderState{};
i32 sampleRate;
i32 channelCount;
i32 decoderOutputBufferSize;
/**
* @brief Holds information about the Opus packet to be decoded
* @note These fields are big-endian
* @url https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/include/switch/services/hwopus.h#L19
*/
struct OpusDataHeader {
u32 sizeBe; //!< Size of the packet following this header
u32 finalRangeBe; //!< Final range of the codec encoder's entropy coder (can be zero)
u32 GetPacketSize() {
return util::SwapEndianness(sizeBe);
}
};
static_assert(sizeof(OpusDataHeader) == 0x8);
/**
* @brief Resets the Opus decoder's internal state
*/
void ResetContext();
/**
* @brief Decodes Opus source data via libopus
*/
Result DecodeInterleavedImpl(ipc::IpcRequest &request, ipc::IpcResponse &response, bool writeDecodeTime = false);
public:
IHardwareOpusDecoder(const DeviceState &state, ServiceManager &manager, i32 sampleRate, i32 channelCount, u32 workBufferSize, KHandle workBufferHandle);
/**
* @brief Decodes the Opus source data, returns decoded data size and decoded sample count
* @url https://switchbrew.org/wiki/Audio_services#DecodeInterleavedOld
*/
Result DecodeInterleavedOld(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Decodes the Opus source data, returns decoded data size, decoded sample count and decode time in microseconds
* @url https://switchbrew.org/wiki/Audio_services#DecodeInterleavedWithPerfOld
*/
Result DecodeInterleavedWithPerfOld(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Decodes the Opus source data, returns decoded data size, decoded sample count and decode time in microseconds
* @note The bool flag indicates whether or not to reset the decoder context
* @url https://switchbrew.org/wiki/Audio_services#DecodeInterleaved
*/
Result DecodeInterleaved(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL(
SFUNC(0x0, IHardwareOpusDecoder, DecodeInterleavedOld),
SFUNC(0x4, IHardwareOpusDecoder, DecodeInterleavedWithPerfOld),
SFUNC(0x6, IHardwareOpusDecoder, DecodeInterleaved), // DecodeInterleavedWithPerfAndResetOld is effectively the same as DecodeInterleaved
SFUNC(0x8, IHardwareOpusDecoder, DecodeInterleaved),
)
};
/**
* @brief A wrapper around libopus error codes for exceptions
*/
class OpusException : public exception {
public:
OpusException(int errorCode) : exception("Opus failed with error code {}: {}", errorCode, opus_strerror(errorCode)) {}
};
}

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <services/serviceman.h>
#include "IHardwareOpusDecoderManager.h"
#include "IHardwareOpusDecoder.h"
namespace skyline::service::codec {
static size_t CalculateBufferSize(i32 sampleRate, i32 channelCount) {
i32 requiredSize{opus_decoder_get_size(channelCount)};
requiredSize += MaxInputBufferSize + CalculateOutBufferSize(sampleRate, channelCount, MaxFrameSizeNormal);
return requiredSize;
}
Result IHardwareOpusDecoderManager::OpenHardwareOpusDecoder(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
i32 sampleRate{request.Pop<i32>()};
i32 channelCount{request.Pop<i32>()};
u32 workBufferSize{request.Pop<u32>()};
KHandle workBuffer{request.copyHandles.at(0)};
state.logger->Debug("Creating Opus decoder: Sample rate: {}, Channel count: {}, Work buffer handle: 0x{:X} (Size: 0x{:X})", sampleRate, channelCount, workBuffer, workBufferSize);
manager.RegisterService(std::make_shared<IHardwareOpusDecoder>(state, manager, sampleRate, channelCount, workBufferSize, workBuffer), session, response);
return {};
}
Result IHardwareOpusDecoderManager::GetWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
i32 sampleRate{request.Pop<i32>()};
i32 channelCount{request.Pop<i32>()};
response.Push<u32>(CalculateBufferSize(sampleRate, channelCount));
return {};
}
}

View File

@ -0,0 +1,47 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/base_service.h>
namespace skyline::service::codec {
/**
* @brief Initialization parameters for the Opus multi-stream decoder
* @see opus_multistream_decoder_init()
*/
struct MultiStreamParameters {
i32 sampleRate;
i32 channelCount;
i32 streamCount;
i32 stereoStreamCount;
std::array<u8, 0x100> mappings; //!< Array of channel mappings
};
static_assert(sizeof(MultiStreamParameters) == 0x110);
/**
* @brief Manages all instances of IHardwareOpusDecoder
* @url https://switchbrew.org/wiki/Audio_services#hwopus
*/
class IHardwareOpusDecoderManager : public BaseService {
public:
IHardwareOpusDecoderManager(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
/**
* @brief Returns an IHardwareOpusDecoder object
* @url https://switchbrew.org/wiki/Audio_services#OpenHardwareOpusDecoder
*/
Result OpenHardwareOpusDecoder(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns the required size for the decoder's work buffer
* @url https://switchbrew.org/wiki/Audio_services#GetWorkBufferSize
*/
Result GetWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL(
SFUNC(0x0, IHardwareOpusDecoderManager, OpenHardwareOpusDecoder),
SFUNC(0x1, IHardwareOpusDecoderManager, GetWorkBufferSize),
)
};
}

View File

@ -11,6 +11,7 @@
#include "am/IAllSystemAppletProxiesService.h"
#include "audio/IAudioOutManager.h"
#include "audio/IAudioRendererManager.h"
#include "codec/IHardwareOpusDecoderManager.h"
#include "fatalsrv/IService.h"
#include "hid/IHidServer.h"
#include "timesrv/IStaticService.h"
@ -66,6 +67,7 @@ namespace skyline::service {
SERVICE_CASE(am::IAllSystemAppletProxiesService, "appletAE")
SERVICE_CASE(audio::IAudioOutManager, "audout:u")
SERVICE_CASE(audio::IAudioRendererManager, "audren:u")
SERVICE_CASE(codec::IHardwareOpusDecoderManager, "hwopus")
SERVICE_CASE(hid::IHidServer, "hid")
SERVICE_CASE(timesrv::IStaticService, "time:s", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemPermissions) // Both of these would be registered after TimeServiceManager::Setup normally but we call that in the GlobalServiceState constructor so can just list them here directly
SERVICE_CASE(timesrv::IStaticService, "time:su", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemUpdatePermissions)

View File

@ -40,8 +40,6 @@ namespace skyline::service {
* @param serviceObject An instance of the service
* @param session The session object of the command
* @param response The response object to write the handle or virtual handle to
* @param submodule If the registered service is a submodule or not
* @param name The name of the service to register if it's not a submodule - it will be added to the service map
*/
void RegisterService(std::shared_ptr<BaseService> serviceObject, type::KSession &session, ipc::IpcResponse &response);

View File

@ -35,6 +35,10 @@ namespace skyline::service::timesrv {
return ns;
}
constexpr i64 Microseconds() const {
return ns / static_cast<i64>(skyline::constant::NsInMicrosecond);
}
constexpr i64 Seconds() const {
return ns / static_cast<i64>(skyline::constant::NsInSecond);
}