mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-09 00:30:41 +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/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
|
||||
|
@ -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
|
||||
|
@ -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 "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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user