mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-09 23:39:26 +01:00
hwopus decoder service implementation
This commit is contained in:
parent
3de4c3e32e
commit
06674b3d07
@ -161,6 +161,8 @@ add_library(skyline SHARED
|
|||||||
${source_DIR}/skyline/services/am/storage/IStorage.cpp
|
${source_DIR}/skyline/services/am/storage/IStorage.cpp
|
||||||
${source_DIR}/skyline/services/am/storage/IStorageAccessor.cpp
|
${source_DIR}/skyline/services/am/storage/IStorageAccessor.cpp
|
||||||
${source_DIR}/skyline/services/am/applet/ILibraryAppletAccessor.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/IHidServer.cpp
|
||||||
${source_DIR}/skyline/services/hid/IAppletResource.cpp
|
${source_DIR}/skyline/services/hid/IAppletResource.cpp
|
||||||
${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp
|
${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp
|
||||||
|
@ -121,6 +121,7 @@ namespace skyline {
|
|||||||
|
|
||||||
namespace constant {
|
namespace constant {
|
||||||
// Time
|
// 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 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second
|
||||||
constexpr u64 NsInMillisecond{1000000}; //!< The amount of nanoseconds in a millisecond
|
constexpr u64 NsInMillisecond{1000000}; //!< The amount of nanoseconds in a millisecond
|
||||||
constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day
|
constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day
|
||||||
|
@ -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 {};
|
||||||
|
}
|
||||||
|
}
|
@ -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)) {}
|
||||||
|
};
|
||||||
|
}
|
@ -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 {};
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
#include "am/IAllSystemAppletProxiesService.h"
|
#include "am/IAllSystemAppletProxiesService.h"
|
||||||
#include "audio/IAudioOutManager.h"
|
#include "audio/IAudioOutManager.h"
|
||||||
#include "audio/IAudioRendererManager.h"
|
#include "audio/IAudioRendererManager.h"
|
||||||
|
#include "codec/IHardwareOpusDecoderManager.h"
|
||||||
#include "fatalsrv/IService.h"
|
#include "fatalsrv/IService.h"
|
||||||
#include "hid/IHidServer.h"
|
#include "hid/IHidServer.h"
|
||||||
#include "timesrv/IStaticService.h"
|
#include "timesrv/IStaticService.h"
|
||||||
@ -66,6 +67,7 @@ namespace skyline::service {
|
|||||||
SERVICE_CASE(am::IAllSystemAppletProxiesService, "appletAE")
|
SERVICE_CASE(am::IAllSystemAppletProxiesService, "appletAE")
|
||||||
SERVICE_CASE(audio::IAudioOutManager, "audout:u")
|
SERVICE_CASE(audio::IAudioOutManager, "audout:u")
|
||||||
SERVICE_CASE(audio::IAudioRendererManager, "audren:u")
|
SERVICE_CASE(audio::IAudioRendererManager, "audren:u")
|
||||||
|
SERVICE_CASE(codec::IHardwareOpusDecoderManager, "hwopus")
|
||||||
SERVICE_CASE(hid::IHidServer, "hid")
|
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: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)
|
SERVICE_CASE(timesrv::IStaticService, "time:su", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemUpdatePermissions)
|
||||||
|
@ -40,8 +40,6 @@ namespace skyline::service {
|
|||||||
* @param serviceObject An instance of the service
|
* @param serviceObject An instance of the service
|
||||||
* @param session The session object of the command
|
* @param session The session object of the command
|
||||||
* @param response The response object to write the handle or virtual handle to
|
* @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);
|
void RegisterService(std::shared_ptr<BaseService> serviceObject, type::KSession &session, ipc::IpcResponse &response);
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ namespace skyline::service::timesrv {
|
|||||||
return ns;
|
return ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr i64 Microseconds() const {
|
||||||
|
return ns / static_cast<i64>(skyline::constant::NsInMicrosecond);
|
||||||
|
}
|
||||||
|
|
||||||
constexpr i64 Seconds() const {
|
constexpr i64 Seconds() const {
|
||||||
return ns / static_cast<i64>(skyline::constant::NsInSecond);
|
return ns / static_cast<i64>(skyline::constant::NsInSecond);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user