sharpemu/src/SharpEmu.Libs/Codec/CodecExports.cs

105 lines
4.3 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Collections.Concurrent;
using System.Threading;
namespace SharpEmu.Libs.Codec;
/// <summary>
/// libSceVideodec / libSceAudiodec handle management. Actual H.264/HEVC and
/// AAC/AT9 decoding requires an external codec, which is out of scope; these
/// exports keep the decoder lifecycle resolvable (create/decode/flush/delete)
/// and report "no output produced" so guests advance instead of failing on
/// unresolved imports.
/// </summary>
public static class CodecExports
{
private const int Ok = 0;
private const int VideodecErrorInvalidArg = unchecked((int)0x80620801);
private const int AudiodecErrorInvalidArg = unchecked((int)0x807F0002);
private static readonly ConcurrentDictionary<ulong, byte> VideoDecoders = new();
private static readonly ConcurrentDictionary<ulong, byte> AudioDecoders = new();
private static long _nextHandle = 1;
// ---- Video decoder ----
[SysAbiExport(Nid = "qkgRiwHyheU", ExportName = "sceVideodecCreateDecoder",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceVideodec")]
public static int VideodecCreateDecoder(CpuContext ctx)
{
var outHandleAddress = ctx[CpuRegister.Rdx];
if (outHandleAddress == 0)
{
return SetReturn(ctx, VideodecErrorInvalidArg);
}
var handle = (ulong)Interlocked.Increment(ref _nextHandle);
VideoDecoders[handle] = 1;
return TryWriteHandle(ctx, outHandleAddress, handle) ? SetReturn(ctx, Ok) : SetReturn(ctx, VideodecErrorInvalidArg);
}
[SysAbiExport(Nid = "q0W5GJMovMs", ExportName = "sceVideodecDecode",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceVideodec")]
public static int VideodecDecode(CpuContext ctx)
{
// No decoder is present: report success with no picture produced.
return SetReturn(ctx, VideoDecoders.ContainsKey(ctx[CpuRegister.Rdi]) ? Ok : VideodecErrorInvalidArg);
}
[SysAbiExport(Nid = "jeigLlKdp5I", ExportName = "sceVideodecFlush",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceVideodec")]
public static int VideodecFlush(CpuContext ctx) =>
SetReturn(ctx, VideoDecoders.ContainsKey(ctx[CpuRegister.Rdi]) ? Ok : VideodecErrorInvalidArg);
[SysAbiExport(Nid = "U0kpGF1cl90", ExportName = "sceVideodecDeleteDecoder",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceVideodec")]
public static int VideodecDeleteDecoder(CpuContext ctx)
{
VideoDecoders.TryRemove(ctx[CpuRegister.Rdi], out _);
return SetReturn(ctx, Ok);
}
// ---- Audio decoder ----
[SysAbiExport(Nid = "O3f1sLMWRvs", ExportName = "sceAudiodecCreateDecoder",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceAudiodec")]
public static int AudiodecCreateDecoder(CpuContext ctx)
{
var handle = (ulong)Interlocked.Increment(ref _nextHandle);
AudioDecoders[handle] = 1;
// sceAudiodec returns the handle directly (>= 0) or a negative error.
ctx[CpuRegister.Rax] = handle;
return unchecked((int)handle);
}
[SysAbiExport(Nid = "KHXHMDLkILw", ExportName = "sceAudiodecDecode",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceAudiodec")]
public static int AudiodecDecode(CpuContext ctx)
{
// No decoder present: report success with zero output samples so the
// caller treats the frame as silent rather than erroring.
return SetReturn(ctx, AudioDecoders.ContainsKey(ctx[CpuRegister.Rdi]) ? Ok : AudiodecErrorInvalidArg);
}
[SysAbiExport(Nid = "Tp+ZEy69mLk", ExportName = "sceAudiodecDeleteDecoder",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceAudiodec")]
public static int AudiodecDeleteDecoder(CpuContext ctx)
{
AudioDecoders.TryRemove(ctx[CpuRegister.Rdi], out _);
return SetReturn(ctx, Ok);
}
private static bool TryWriteHandle(CpuContext ctx, ulong address, ulong handle) =>
ctx.TryWriteUInt64(address, handle);
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}