From c5bddfeab8a905ce6c306ffafb079d8550f0b027 Mon Sep 17 00:00:00 2001 From: Mary Date: Thu, 14 Jul 2022 15:13:23 +0200 Subject: [PATCH] Remove dependency for FFmpeg.AutoGen and Update FFmpeg to 5.0.1 for Windows (#3466) * Remove dependency for FFMpeg.AutoGen Also prepare for FFMpeg 5.0 and 5.1 * Update Ryujinx.Graphics.Nvdec.Dependencies to 5.0.1-build10 * Address gdkchan's comments * Address Ack's comment * Address gdkchan's comment --- Ryujinx.Ava/Ryujinx.Ava.csproj | 2 +- .../FFmpegContext.cs | 121 ++++++------- Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs | 2 +- .../Native/AVCodec.cs | 25 +++ .../Native/AVCodecContext.cs | 171 ++++++++++++++++++ .../Native/AVCodecID.cs | 8 + .../Native/AVCodecLegacy.cs | 26 +++ .../Native/AVFrame.cs | 37 ++++ Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs | 15 ++ .../Native/AVPacket.cs | 26 +++ .../Native/AVRational.cs | 8 + .../Native/FFCodec.cs | 23 +++ .../Native/FFCodecLegacy.cs | 23 +++ .../Native/FFmpegApi.cs | 129 +++++++++++++ .../Ryujinx.Graphics.Nvdec.FFmpeg.csproj | 6 +- Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs | 24 +-- Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs | 2 +- .../Ryujinx.Headless.SDL2.csproj | 2 +- Ryujinx/Ryujinx.csproj | 2 +- 19 files changed, 563 insertions(+), 89 deletions(-) create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecLegacy.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs diff --git a/Ryujinx.Ava/Ryujinx.Ava.csproj b/Ryujinx.Ava/Ryujinx.Ava.csproj index 19b66029e..2f0f342d4 100644 --- a/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -28,7 +28,7 @@ - + diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs index e0ef05a52..8c2e94c34 100644 --- a/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs @@ -1,23 +1,21 @@ -using FFmpeg.AutoGen; -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; using System; -using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Nvdec.FFmpeg { unsafe class FFmpegContext : IDisposable { - private readonly AVCodec_decode _decodeFrame; - private static readonly av_log_set_callback_callback _logFunc; + private readonly FFCodec.AVCodec_decode _decodeFrame; + private static readonly FFmpegApi.av_log_set_callback_callback _logFunc; private readonly AVCodec* _codec; private AVPacket* _packet; private AVCodecContext* _context; public FFmpegContext(AVCodecID codecId) { - _codec = ffmpeg.avcodec_find_decoder(codecId); + _codec = FFmpegApi.avcodec_find_decoder(codecId); if (_codec == null) { Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation."); @@ -25,7 +23,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg return; } - _context = ffmpeg.avcodec_alloc_context3(_codec); + _context = FFmpegApi.avcodec_alloc_context3(_codec); if (_context == null) { Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated."); @@ -33,14 +31,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg return; } - if (ffmpeg.avcodec_open2(_context, _codec, null) != 0) + if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0) { Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened."); return; } - _packet = ffmpeg.av_packet_alloc(); + _packet = FFmpegApi.av_packet_alloc(); if (_packet == null) { Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated."); @@ -48,52 +46,39 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg return; } - _decodeFrame = Marshal.GetDelegateForFunctionPointer(_codec->decode.Pointer); + int avCodecRawVersion = FFmpegApi.avcodec_version(); + int avCodecMajorVersion = avCodecRawVersion >> 16; + int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF; + + // libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union. + if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24)) + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodec*)_codec)->CodecCallback); + } + // libavcodec 59.x changed AvCodec private API layout. + else if (avCodecMajorVersion == 59) + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodecLegacy*)_codec)->Decode); + } + // libavcodec 58.x and lower + else + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodecLegacy*)_codec)->Decode); + } } static FFmpegContext() { - SetRootPath(); - _logFunc = Log; // Redirect log output. - ffmpeg.av_log_set_level(ffmpeg.AV_LOG_MAX_OFFSET); - ffmpeg.av_log_set_callback(_logFunc); + FFmpegApi.av_log_set_level(AVLog.MaxOffset); + FFmpegApi.av_log_set_callback(_logFunc); } - private static void SetRootPath() + private static void Log(void* ptr, AVLog level, string format, byte* vl) { - if (OperatingSystem.IsLinux()) - { - // Configure FFmpeg search path - Process lddProcess = Process.Start(new ProcessStartInfo - { - FileName = "/bin/sh", - Arguments = "-c \"ldd $(which ffmpeg 2>/dev/null) | grep libavfilter\" 2>/dev/null", - UseShellExecute = false, - RedirectStandardOutput = true - }); - - string lddOutput = lddProcess.StandardOutput.ReadToEnd(); - - lddProcess.WaitForExit(); - lddProcess.Close(); - - if (lddOutput.Contains(" => ")) - { - ffmpeg.RootPath = Path.GetDirectoryName(lddOutput.Split(" => ")[1]); - } - else - { - Logger.Error?.PrintMsg(LogClass.FFmpeg, "FFmpeg wasn't found. Make sure that you have it installed and up to date."); - } - } - } - - private static void Log(void* p0, int level, string format, byte* vl) - { - if (level > ffmpeg.av_log_get_level()) + if (level > FFmpegApi.av_log_get_level()) { return; } @@ -102,65 +87,67 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg byte* lineBuffer = stackalloc byte[lineSize]; int printPrefix = 1; - ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix); + FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix); string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim(); switch (level) { - case ffmpeg.AV_LOG_PANIC: - case ffmpeg.AV_LOG_FATAL: - case ffmpeg.AV_LOG_ERROR: + case AVLog.Panic: + case AVLog.Fatal: + case AVLog.Error: Logger.Error?.Print(LogClass.FFmpeg, line); break; - case ffmpeg.AV_LOG_WARNING: + case AVLog.Warning: Logger.Warning?.Print(LogClass.FFmpeg, line); break; - case ffmpeg.AV_LOG_INFO: + case AVLog.Info: Logger.Info?.Print(LogClass.FFmpeg, line); break; - case ffmpeg.AV_LOG_VERBOSE: - case ffmpeg.AV_LOG_DEBUG: - case ffmpeg.AV_LOG_TRACE: + case AVLog.Verbose: + case AVLog.Debug: Logger.Debug?.Print(LogClass.FFmpeg, line); break; + case AVLog.Trace: + Logger.Trace?.Print(LogClass.FFmpeg, line); + break; } } public int DecodeFrame(Surface output, ReadOnlySpan bitstream) { - ffmpeg.av_frame_unref(output.Frame); + FFmpegApi.av_frame_unref(output.Frame); int result; int gotFrame; fixed (byte* ptr = bitstream) { - _packet->data = ptr; - _packet->size = bitstream.Length; + _packet->Data = ptr; + _packet->Size = bitstream.Length; result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); } if (gotFrame == 0) { - ffmpeg.av_frame_unref(output.Frame); + FFmpegApi.av_frame_unref(output.Frame); // If the frame was not delivered, it was probably delayed. // Get the next delayed frame by passing a 0 length packet. - _packet->data = null; - _packet->size = 0; + _packet->Data = null; + _packet->Size = 0; result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); // We need to set B frames to 0 as we already consumed all delayed frames. // This prevents the decoder from trying to return a delayed frame next time. - _context->has_b_frames = 0; + _context->HasBFrames = 0; } - ffmpeg.av_packet_unref(_packet); + FFmpegApi.av_packet_unref(_packet); if (gotFrame == 0) { - ffmpeg.av_frame_unref(output.Frame); + FFmpegApi.av_frame_unref(output.Frame); return -1; } @@ -172,14 +159,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg { fixed (AVPacket** ppPacket = &_packet) { - ffmpeg.av_packet_free(ppPacket); + FFmpegApi.av_packet_free(ppPacket); } - ffmpeg.avcodec_close(_context); + FFmpegApi.avcodec_close(_context); fixed (AVCodecContext** ppContext = &_context) { - ffmpeg.avcodec_free_context(ppContext); + FFmpegApi.avcodec_free_context(ppContext); } } } diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs index 8deda42a5..d8b213e55 100644 --- a/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs @@ -1,4 +1,4 @@ -using FFmpeg.AutoGen; +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; using Ryujinx.Graphics.Video; using System; diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs new file mode 100644 index 000000000..93edb7883 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodec + { +#pragma warning disable CS0649 + public unsafe byte* Name; + public unsafe byte* LongName; + public int Type; + public AVCodecID Id; + public int Capabilities; + public byte MaxLowRes; + public unsafe AVRational* SupportedFramerates; + public IntPtr PixFmts; + public IntPtr SupportedSamplerates; + public IntPtr SampleFmts; + // Deprecated + public unsafe ulong* ChannelLayouts; + public unsafe IntPtr PrivClass; + public IntPtr Profiles; + public unsafe byte* WrapperName; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs new file mode 100644 index 000000000..11bd63b07 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs @@ -0,0 +1,171 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodecContext + { +#pragma warning disable CS0649 + public unsafe IntPtr AvClass; + public int LogLevelOffset; + public int CodecType; + public unsafe AVCodecLegacy* Codec; + public AVCodecID CodecId; + public uint CodecTag; + public IntPtr PrivData; + public IntPtr Internal; + public IntPtr Opaque; + public long BitRate; + public int BitRateTolerance; + public int GlobalQuality; + public int CompressionLevel; + public int Flags; + public int Flags2; + public IntPtr ExtraData; + public int ExtraDataSize; + public AVRational TimeBase; + public int TicksPerFrame; + public int Delay; + public int Width; + public int Height; + public int CodedWidth; + public int CodedHeight; + public int GopSize; + public int PixFmt; + public IntPtr DrawHorizBand; + public IntPtr GetFormat; + public int MaxBFrames; + public float BQuantFactor; + public float BQuantOffset; + public int HasBFrames; + public float IQuantFactor; + public float IQuantOffset; + public float LumiMasking; + public float TemporalCplxMasking; + public float SpatialCplxMasking; + public float PMasking; + public float DarkMasking; + public int SliceCount; + public IntPtr SliceOffset; + public AVRational SampleAspectRatio; + public int MeCmp; + public int MeSubCmp; + public int MbCmp; + public int IldctCmp; + public int DiaSize; + public int LastPredictorCount; + public int MePreCmp; + public int PreDiaSize; + public int MeSubpelQuality; + public int MeRange; + public int SliceFlags; + public int MbDecision; + public IntPtr IntraMatrix; + public IntPtr InterMatrix; + public int IntraDcPrecision; + public int SkipTop; + public int SkipBottom; + public int MbLmin; + public int MbLmax; + public int BidirRefine; + public int KeyintMin; + public int Refs; + public int Mv0Threshold; + public int ColorPrimaries; + public int ColorPrc; + public int Colorspace; + public int ColorRange; + public int ChromaSampleLocation; + public int Slices; + public int FieldOrder; + public int SampleRate; + public int Channels; + public int SampleFmt; + public int FrameSize; + public int FrameNumber; + public int BlockAlign; + public int CutOff; + public ulong ChannelLayout; + public ulong RequestChannelLayout; + public int AudioServiceType; + public int RequestSampleFmt; + public IntPtr GetBuffer2; + public float QCompress; + public float QBlur; + public int QMin; + public int QMax; + public int MaxQdiff; + public int RcBufferSize; + public int RcOverrideCount; + public IntPtr RcOverride; + public long RcMaxRate; + public long RcMinRate; + public float RcMax_available_vbv_use; + public float RcMin_vbv_overflow_use; + public int RcInitialBufferOccupancy; + public int Trellis; + public IntPtr StatsOut; + public IntPtr StatsIn; + public int WorkaroundBugs; + public int StrictStdCompliance; + public int ErrorConcealment; + public int Debug; + public int ErrRecognition; + public long ReorderedOpaque; + public IntPtr HwAccel; + public IntPtr HwAccelContext; + public Array8 Error; + public int DctAlgo; + public int IdctAlgo; + public int BitsPerCodedSample; + public int BitsPerRawSample; + public int LowRes; + public int ThreadCount; + public int ThreadType; + public int ActiveThreadType; + public int ThreadSafeCallbacks; + public IntPtr Execute; + public IntPtr Execute2; + public int NsseWeight; + public int Profile; + public int Level; + public int SkipLoopFilter; + public int SkipIdct; + public int SkipFrame; + public IntPtr SubtitleHeader; + public int SubtitleHeaderSize; + public int InitialPadding; + public AVRational Framerate; + public int SwPixFmt; + public AVRational PktTimebase; + public IntPtr CodecDescriptor; + public long PtsCorrectionNumFaultyPts; + public long PtsCorrectionNumFaultyDts; + public long PtsCorrectionLastPts; + public long PtsCorrectionLastDts; + public IntPtr SubCharenc; + public int SubCharencMode; + public int SkipAlpha; + public int SeekPreroll; + public int DebugMv; + public IntPtr ChromaIntraMatrix; + public IntPtr DumpSeparator; + public IntPtr CodecWhitelist; + public uint Properties; + public IntPtr CodedSideData; + public int NbCodedSideData; + public IntPtr HwFramesCtx; + public int SubTextFormat; + public int TrailingPadding; + public long MaxPixels; + public IntPtr HwDeviceCtx; + public int HwAccelFlags; + public int applyCropping; + public int ExtraHwFrames; + public int DiscardDamagedPercentage; + public long MaxSamples; + public int ExportSideData; + public IntPtr GetEncodeBuffer; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs new file mode 100644 index 000000000..b371de9e8 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + enum AVCodecID + { + AV_CODEC_ID_H264 = 27, + AV_CODEC_ID_VP8 = 139, + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecLegacy.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecLegacy.cs new file mode 100644 index 000000000..0913cbc4b --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecLegacy.cs @@ -0,0 +1,26 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodecLegacy + { +#pragma warning disable CS0649 + public unsafe byte* Name; + public unsafe byte* LongName; + public int Type; + public AVCodecID Id; + public int Capabilities; + public byte MaxLowRes; + public unsafe AVRational* SupportedFramerates; + public IntPtr PixFmts; + public IntPtr SupportedSamplerates; + public IntPtr SampleFmts; + // Deprecated + public unsafe ulong* ChannelLayouts; + public unsafe IntPtr PrivClass; + public IntPtr Profiles; + public unsafe byte* WrapperName; + public IntPtr ChLayouts; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs new file mode 100644 index 000000000..faaf5c7d8 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVFrame + { +#pragma warning disable CS0649 + public Array8 Data; + public Array8 LineSize; + public IntPtr ExtendedData; + public int Width; + public int Height; + public int NumSamples; + public int Format; + public int KeyFrame; + public int PictureType; + public AVRational SampleAspectRatio; + public long Pts; + public long PktDts; + public AVRational TimeBase; + public int CodedPictureNumber; + public int DisplayPictureNumber; + public int Quality; + public IntPtr Opaque; + public int RepeatPicture; + public int InterlacedFrame; + public int TopFieldFirst; + public int PaletteHasChanged; + public long ReorderedOpaque; + public int SampleRate; + public ulong ChannelLayout; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs new file mode 100644 index 000000000..4224de9eb --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + enum AVLog + { + Panic = 0, + Fatal = 8, + Error = 16, + Warning = 24, + Info = 32, + Verbose = 40, + Debug = 48, + Trace = 56, + MaxOffset = 64 + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs new file mode 100644 index 000000000..d5b021040 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs @@ -0,0 +1,26 @@ +using System; + +using AVBufferRef = System.IntPtr; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVPacket + { +#pragma warning disable CS0649 + public unsafe AVBufferRef *Buf; + public long Pts; + public long Dts; + public unsafe byte* Data; + public int Size; + public int StreamIndex; + public int Flags; + public IntPtr SizeData; + public int SizeDataElems; + public long Duration; + public long Position; + public IntPtr Opaque; + public unsafe AVBufferRef *OpaqueRef; + public AVRational TimeBase; +#pragma warning restore CS0649 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs new file mode 100644 index 000000000..cad5fde0b --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + public struct AVRational + { + public int Numerator; + public int Denominator; + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs new file mode 100644 index 000000000..8b08c02c0 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct FFCodec + { + public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt); + +#pragma warning disable CS0649 + public AVCodec Base; + public int CapsInternalOrCbType; + public int PrivDataSize; + public IntPtr UpdateThreadContext; + public IntPtr UpdateThreadContextForUser; + public IntPtr Defaults; + public IntPtr InitStaticData; + public IntPtr Init; + public IntPtr CodecCallback; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs new file mode 100644 index 000000000..910270a55 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct FFCodecLegacy where T: struct + { +#pragma warning disable CS0649 + public T Base; + public uint CapsInternalOrCbType; + public int PrivDataSize; + public IntPtr UpdateThreadContext; + public IntPtr UpdateThreadContextForUser; + public IntPtr Defaults; + public IntPtr InitStaticData; + public IntPtr Init; + public IntPtr EncodeSub; + public IntPtr Encode2; + public IntPtr Decode; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs new file mode 100644 index 000000000..4c0107831 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + static class FFmpegApi + { + public const string AvCodecLibraryName = "avcodec"; + public const string AvUtilLibraryName = "avutil"; + + private static readonly Dictionary _librariesWhitelist = new Dictionary + { + { AvCodecLibraryName, (58, 59) }, + { AvUtilLibraryName, (56, 57) } + }; + + private static string FormatLibraryNameForCurrentOs(string libraryName, int version) + { + if (OperatingSystem.IsWindows()) + { + return $"{libraryName}-{version}.dll"; + } + else if (OperatingSystem.IsLinux()) + { + return $"lib{libraryName}.so.{version}"; + } + else if (OperatingSystem.IsMacOS()) + { + return $"lib{libraryName}.{version}.dylib"; + } + else + { + throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}"); + } + } + + + private static bool TryLoadWhitelistedLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle) + { + handle = IntPtr.Zero; + + if (_librariesWhitelist.TryGetValue(libraryName, out var value)) + { + (int minVersion, int maxVersion) = value; + + for (int version = minVersion; version <= maxVersion; version++) + { + if (NativeLibrary.TryLoad(FormatLibraryNameForCurrentOs(libraryName, version), assembly, searchPath, out handle)) + { + return true; + } + } + } + + return false; + } + + static FFmpegApi() + { + NativeLibrary.SetDllImportResolver(typeof(FFmpegApi).Assembly, (name, assembly, path) => + { + IntPtr handle; + + if (name == AvUtilLibraryName && TryLoadWhitelistedLibrary(AvUtilLibraryName, assembly, path, out handle)) + { + return handle; + } + else if (name == AvCodecLibraryName && TryLoadWhitelistedLibrary(AvCodecLibraryName, assembly, path, out handle)) + { + return handle; + } + + return IntPtr.Zero; + }); + } + + public unsafe delegate void av_log_set_callback_callback(void* a0, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string a2, byte* a3); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern AVFrame* av_frame_alloc(); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_frame_unref(AVFrame* frame); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_free(AVFrame* frame); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_log_set_level(AVLog level); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_log_set_callback(av_log_set_callback_callback callback); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern AVLog av_log_get_level(); + + [DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_log_format_line(void* ptr, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string fmt, byte* vl, byte* line, int lineSize, int* printPrefix); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern AVCodec* avcodec_find_decoder(AVCodecID id); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern AVCodecContext* avcodec_alloc_context3(AVCodec* codec); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int avcodec_open2(AVCodecContext* avctx, AVCodec* codec, void **options); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int avcodec_close(AVCodecContext* avctx); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void avcodec_free_context(AVCodecContext** avctx); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern AVPacket* av_packet_alloc(); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_packet_unref(AVPacket* pkt); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern void av_packet_free(AVPacket** pkt); + + [DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int avcodec_version(); + } +} diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj b/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj index 4e90eb205..b30e6aea0 100644 --- a/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj @@ -1,14 +1,10 @@ - + net6.0 true - - - - diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs index 13d0df498..1ca9d1d53 100644 --- a/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs @@ -1,4 +1,4 @@ -using FFmpeg.AutoGen; +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; using Ryujinx.Graphics.Video; using System; @@ -11,31 +11,31 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg public int RequestedWidth { get; } public int RequestedHeight { get; } - public Plane YPlane => new Plane((IntPtr)Frame->data[0], Stride * Height); - public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight); - public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight); + public Plane YPlane => new Plane((IntPtr)Frame->Data[0], Stride * Height); + public Plane UPlane => new Plane((IntPtr)Frame->Data[1], UvStride * UvHeight); + public Plane VPlane => new Plane((IntPtr)Frame->Data[2], UvStride * UvHeight); - public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive; + public FrameField Field => Frame->InterlacedFrame != 0 ? FrameField.Interlaced : FrameField.Progressive; - public int Width => Frame->width; - public int Height => Frame->height; - public int Stride => Frame->linesize[0]; + public int Width => Frame->Width; + public int Height => Frame->Height; + public int Stride => Frame->LineSize[0]; public int UvWidth => (Width + 1) >> 1; public int UvHeight => (Height + 1) >> 1; - public int UvStride => Frame->linesize[1]; + public int UvStride => Frame->LineSize[1]; public Surface(int width, int height) { RequestedWidth = width; RequestedHeight = height; - Frame = ffmpeg.av_frame_alloc(); + Frame = FFmpegApi.av_frame_alloc(); } public void Dispose() { - ffmpeg.av_frame_unref(Frame); - ffmpeg.av_free(Frame); + FFmpegApi.av_frame_unref(Frame); + FFmpegApi.av_free(Frame); } } } diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs index f12de2875..3570c3ecc 100644 --- a/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs +++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs @@ -1,4 +1,4 @@ -using FFmpeg.AutoGen; +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; using Ryujinx.Graphics.Video; using System; diff --git a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index 092ec3c22..2e9fd1a05 100644 --- a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -10,7 +10,7 @@ - + diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index ed2fa1c49..4dc50f9f2 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -19,7 +19,7 @@ - +