mirror of
https://github.com/Ryubing/Ryujinx.git
synced 2025-12-04 02:12:32 -05:00
Migrate Audio service to new IPC (#6285)
* Migrate audren to new IPC * Migrate audout * Migrate audin * Migrate hwopus * Bye bye old audio service * Switch volume control to IHardwareDeviceDriver * Somewhat unrelated changes * Remove Concentus reference from HLE * Implement OpenAudioRendererForManualExecution * Remove SetVolume/GetVolume methods that are not necessary * Remove SetVolume/GetVolume methods that are not necessary (2) * Fix incorrect volume update * PR feedback * PR feedback * Stub audrec * Init outParameter * Make FinalOutputRecorderParameter/Internal readonly * Make FinalOutputRecorder IDisposable * Fix HardwareOpusDecoderManager parameter buffers * Opus work buffer size and error handling improvements * Add AudioInProtocolName enum * Fix potential divisions by zero
This commit is contained in:
16
src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec
|
||||
{
|
||||
static class CodecResult
|
||||
{
|
||||
private const int ModuleId = 111;
|
||||
|
||||
public static Result InvalidLength => new(ModuleId, 3);
|
||||
public static Result OpusBadArg => new(ModuleId, 130);
|
||||
public static Result OpusInvalidPacket => new(ModuleId, 133);
|
||||
public static Result InvalidNumberOfStreams => new(ModuleId, 1000);
|
||||
public static Result InvalidSampleRate => new(ModuleId, 1001);
|
||||
public static Result InvalidChannelCount => new(ModuleId, 1002);
|
||||
}
|
||||
}
|
||||
336
src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
Normal file
336
src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using Concentus;
|
||||
using Concentus.Enums;
|
||||
using Concentus.Structs;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct OpusPacketHeader
|
||||
{
|
||||
public uint Length;
|
||||
public uint FinalRange;
|
||||
|
||||
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Length = BinaryPrimitives.ReadUInt32BigEndian(data),
|
||||
FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private interface IDecoder
|
||||
{
|
||||
int SampleRate { get; }
|
||||
int ChannelsCount { get; }
|
||||
|
||||
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
|
||||
void ResetState();
|
||||
}
|
||||
|
||||
private class Decoder : IDecoder
|
||||
{
|
||||
private readonly OpusDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount => _decoder.NumChannels;
|
||||
|
||||
public Decoder(int sampleRate, int channelsCount)
|
||||
{
|
||||
_decoder = new OpusDecoder(sampleRate, channelsCount);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
private class MultiSampleDecoder : IDecoder
|
||||
{
|
||||
private readonly OpusMSDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount { get; }
|
||||
|
||||
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
|
||||
{
|
||||
ChannelsCount = channelsCount;
|
||||
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IDecoder _decoder;
|
||||
private int _workBufferHandle;
|
||||
|
||||
private HardwareOpusDecoder(int workBufferHandle)
|
||||
{
|
||||
_workBufferHandle = workBufferHandle;
|
||||
}
|
||||
|
||||
public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle)
|
||||
{
|
||||
_decoder = new Decoder(sampleRate, channelsCount);
|
||||
}
|
||||
|
||||
public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle)
|
||||
{
|
||||
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
}
|
||||
|
||||
[CmifCommand(0)]
|
||||
public Result DecodeInterleavedOld(
|
||||
out int outConsumed,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
[CmifCommand(1)]
|
||||
public Result SetContext(ReadOnlySpan<byte> context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(2)] // 3.0.0+
|
||||
public Result DecodeInterleavedForMultiStreamOld(
|
||||
out int outConsumed,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
[CmifCommand(3)] // 3.0.0+
|
||||
public Result SetContextForMultiStream(ReadOnlySpan<byte> arg0)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(4)] // 4.0.0+
|
||||
public Result DecodeInterleavedWithPerfOld(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
[CmifCommand(5)] // 4.0.0+
|
||||
public Result DecodeInterleavedForMultiStreamWithPerfOld(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
[CmifCommand(6)] // 6.0.0+
|
||||
public Result DecodeInterleavedWithPerfAndResetOld(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
|
||||
bool reset)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CmifCommand(7)] // 6.0.0+
|
||||
public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
|
||||
bool reset)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CmifCommand(8)] // 7.0.0+
|
||||
public Result DecodeInterleaved(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
|
||||
bool reset)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CmifCommand(9)] // 7.0.0+
|
||||
public Result DecodeInterleavedForMultiStream(
|
||||
out int outConsumed,
|
||||
out long timeTaken,
|
||||
out int outSamples,
|
||||
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
|
||||
bool reset)
|
||||
{
|
||||
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
|
||||
}
|
||||
|
||||
private Result DecodeInterleavedInternal(
|
||||
out int outConsumed,
|
||||
out int outSamples,
|
||||
out long timeTaken,
|
||||
Span<byte> output,
|
||||
ReadOnlySpan<byte> input,
|
||||
bool reset,
|
||||
bool withPerf)
|
||||
{
|
||||
timeTaken = 0;
|
||||
|
||||
Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
|
||||
|
||||
if (withPerf)
|
||||
{
|
||||
// This is the time the DSP took to process the request, TODO: fill this.
|
||||
timeTaken = 0;
|
||||
}
|
||||
|
||||
MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
|
||||
{
|
||||
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
|
||||
|
||||
numSamples = result;
|
||||
|
||||
if (result == OpusError.OPUS_INVALID_PACKET)
|
||||
{
|
||||
return CodecResult.OpusInvalidPacket;
|
||||
}
|
||||
else if (result == OpusError.OPUS_BAD_ARG)
|
||||
{
|
||||
return CodecResult.OpusBadArg;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static Result DecodeInterleaved(
|
||||
IDecoder decoder,
|
||||
bool reset,
|
||||
ReadOnlySpan<byte> input,
|
||||
out short[] outPcmData,
|
||||
int outputSize,
|
||||
out int outConsumed,
|
||||
out int outSamples)
|
||||
{
|
||||
outPcmData = null;
|
||||
outConsumed = 0;
|
||||
outSamples = 0;
|
||||
|
||||
int streamSize = input.Length;
|
||||
|
||||
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
|
||||
{
|
||||
return CodecResult.InvalidLength;
|
||||
}
|
||||
|
||||
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
|
||||
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
|
||||
uint totalSize = header.Length + (uint)headerSize;
|
||||
|
||||
if (totalSize > streamSize)
|
||||
{
|
||||
return CodecResult.InvalidLength;
|
||||
}
|
||||
|
||||
byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
|
||||
|
||||
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
|
||||
{
|
||||
return CodecResult.InvalidLength;
|
||||
}
|
||||
|
||||
outPcmData = new short[numSamples * decoder.ChannelsCount];
|
||||
|
||||
if (reset)
|
||||
{
|
||||
decoder.ResetState();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
|
||||
outConsumed = (int)totalSize;
|
||||
}
|
||||
catch (OpusException)
|
||||
{
|
||||
// TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
|
||||
return CodecResult.InvalidLength;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_workBufferHandle != 0)
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
|
||||
|
||||
_workBufferHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager
|
||||
{
|
||||
[CmifCommand(0)]
|
||||
public Result OpenHardwareOpusDecoder(
|
||||
out IHardwareOpusDecoder decoder,
|
||||
HardwareOpusDecoderParameterInternal parameter,
|
||||
[CopyHandle] int workBufferHandle,
|
||||
int workBufferSize)
|
||||
{
|
||||
decoder = null;
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
if (!IsValidChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(1)]
|
||||
public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
if (!IsValidChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount);
|
||||
|
||||
int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
|
||||
int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
|
||||
size = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(2)] // 3.0.0+
|
||||
public Result OpenHardwareOpusDecoderForMultiStream(
|
||||
out IHardwareOpusDecoder decoder,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter,
|
||||
[CopyHandle] int workBufferHandle,
|
||||
int workBufferSize)
|
||||
{
|
||||
decoder = null;
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
if (!IsValidMultiChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidNumberOfStreams;
|
||||
}
|
||||
|
||||
decoder = new HardwareOpusDecoder(
|
||||
parameter.SampleRate,
|
||||
parameter.ChannelsCount,
|
||||
parameter.NumberOfStreams,
|
||||
parameter.NumberOfStereoStreams,
|
||||
parameter.ChannelMappings.AsSpan().ToArray(),
|
||||
workBufferHandle);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(3)] // 3.0.0+
|
||||
public Result GetWorkBufferSizeForMultiStream(
|
||||
out int size,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
if (!IsValidMultiChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
|
||||
|
||||
int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
|
||||
int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
|
||||
int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
|
||||
size = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(4)] // 12.0.0+
|
||||
public Result OpenHardwareOpusDecoderEx(
|
||||
out IHardwareOpusDecoder decoder,
|
||||
HardwareOpusDecoderParameterInternalEx parameter,
|
||||
[CopyHandle] int workBufferHandle,
|
||||
int workBufferSize)
|
||||
{
|
||||
decoder = null;
|
||||
|
||||
if (!IsValidChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(5)] // 12.0.0+
|
||||
public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
|
||||
{
|
||||
return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false);
|
||||
}
|
||||
|
||||
[CmifCommand(6)] // 12.0.0+
|
||||
public Result OpenHardwareOpusDecoderForMultiStreamEx(
|
||||
out IHardwareOpusDecoder decoder,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter,
|
||||
[CopyHandle] int workBufferHandle,
|
||||
int workBufferSize)
|
||||
{
|
||||
decoder = null;
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
if (!IsValidMultiChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
|
||||
{
|
||||
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||
|
||||
return CodecResult.InvalidNumberOfStreams;
|
||||
}
|
||||
|
||||
decoder = new HardwareOpusDecoder(
|
||||
parameter.SampleRate,
|
||||
parameter.ChannelsCount,
|
||||
parameter.NumberOfStreams,
|
||||
parameter.NumberOfStereoStreams,
|
||||
parameter.ChannelMappings.AsSpan().ToArray(),
|
||||
workBufferHandle);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(7)] // 12.0.0+
|
||||
public Result GetWorkBufferSizeForMultiStreamEx(
|
||||
out int size,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
|
||||
{
|
||||
return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false);
|
||||
}
|
||||
|
||||
[CmifCommand(8)] // 16.0.0+
|
||||
public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
|
||||
{
|
||||
return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true);
|
||||
}
|
||||
|
||||
[CmifCommand(9)] // 16.0.0+
|
||||
public Result GetWorkBufferSizeForMultiStreamExEx(
|
||||
out int size,
|
||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
|
||||
{
|
||||
return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true);
|
||||
}
|
||||
|
||||
private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
if (!IsValidChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount);
|
||||
|
||||
int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
|
||||
int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
|
||||
size = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
if (!IsValidMultiChannelCount(parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidChannelCount;
|
||||
}
|
||||
|
||||
if (!IsValidSampleRate(parameter.SampleRate))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
|
||||
{
|
||||
return CodecResult.InvalidSampleRate;
|
||||
}
|
||||
|
||||
int opusDecoderSize = fromDsp
|
||||
? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams)
|
||||
: GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
|
||||
|
||||
int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
|
||||
int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
|
||||
int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
|
||||
size = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static int GetDspOpusDecoderSize(int channelsCount)
|
||||
{
|
||||
// TODO: Figure out the size returned here.
|
||||
// Not really important because we don't use the work buffer, and the size being lower is fine.
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams)
|
||||
{
|
||||
// TODO: Figure out the size returned here.
|
||||
// Not really important because we don't use the work buffer, and the size being lower is fine.
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetOpusDecoderSize(int channelsCount)
|
||||
{
|
||||
const int SilkDecoderSize = 0x2160;
|
||||
|
||||
if (channelsCount < 1 || channelsCount > 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int celtDecoderSize = GetCeltDecoderSize(channelsCount);
|
||||
int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x50;
|
||||
|
||||
return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
|
||||
}
|
||||
|
||||
private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
|
||||
{
|
||||
if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coupledSize = GetOpusDecoderSize(2);
|
||||
int monoSize = GetOpusDecoderSize(1);
|
||||
|
||||
return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
|
||||
Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb920;
|
||||
}
|
||||
|
||||
private static int Align4(int value)
|
||||
{
|
||||
return BitUtils.AlignUp(value, 4);
|
||||
}
|
||||
|
||||
private static int GetOpusDecoderAllocSize(int channelsCount)
|
||||
{
|
||||
return channelsCount * 0x800 + 0x4800;
|
||||
}
|
||||
|
||||
private static int GetCeltDecoderSize(int channelsCount)
|
||||
{
|
||||
const int DecodeBufferSize = 0x2030;
|
||||
const int Overlap = 120;
|
||||
const int EBandsCount = 21;
|
||||
|
||||
return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x54;
|
||||
}
|
||||
|
||||
private static bool IsValidChannelCount(int channelsCount)
|
||||
{
|
||||
return channelsCount > 0 && channelsCount <= 2;
|
||||
}
|
||||
|
||||
private static bool IsValidMultiChannelCount(int channelsCount)
|
||||
{
|
||||
return channelsCount > 0 && channelsCount <= 255;
|
||||
}
|
||||
|
||||
private static bool IsValidSampleRate(int sampleRate)
|
||||
{
|
||||
switch (sampleRate)
|
||||
{
|
||||
case 8000:
|
||||
case 12000:
|
||||
case 16000:
|
||||
case 24000:
|
||||
case 48000:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount)
|
||||
{
|
||||
return numberOfStreams > 0 &&
|
||||
numberOfStreams + numberOfStereoStreams <= channelsCount &&
|
||||
numberOfStereoStreams >= 0 &&
|
||||
numberOfStereoStreams <= numberOfStreams;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
|
||||
struct HardwareOpusDecoderParameterInternal
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
|
||||
struct HardwareOpusDecoderParameterInternalEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public OpusDecoderFlags Flags;
|
||||
public uint Reserved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x110)]
|
||||
struct HardwareOpusMultiStreamDecoderParameterInternal
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public Array256<byte> ChannelMappings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x118)]
|
||||
struct HardwareOpusMultiStreamDecoderParameterInternalEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public OpusDecoderFlags Flags;
|
||||
public uint Reserved;
|
||||
public Array256<byte> ChannelMappings;
|
||||
}
|
||||
}
|
||||
20
src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
Normal file
20
src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
interface IHardwareOpusDecoder : IServiceObject
|
||||
{
|
||||
Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
|
||||
Result SetContext(ReadOnlySpan<byte> context);
|
||||
Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
|
||||
Result SetContextForMultiStream(ReadOnlySpan<byte> context);
|
||||
Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
|
||||
Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
|
||||
Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
|
||||
Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
|
||||
Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
|
||||
Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
interface IHardwareOpusDecoderManager : IServiceObject
|
||||
{
|
||||
Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
|
||||
Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter);
|
||||
Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
|
||||
Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter);
|
||||
Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
|
||||
Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
|
||||
Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
|
||||
Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
|
||||
Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
|
||||
Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
|
||||
}
|
||||
}
|
||||
11
src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[Flags]
|
||||
enum OpusDecoderFlags : uint
|
||||
{
|
||||
None,
|
||||
LargeFrameSize = 1 << 0,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user