aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs')
-rw-r--r--src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
new file mode 100644
index 00000000..572ceaaa
--- /dev/null
+++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
@@ -0,0 +1,175 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Nvdec.FFmpeg
+{
+ unsafe class FFmpegContext : IDisposable
+ {
+ private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
+
+ private readonly 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 = 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.");
+
+ return;
+ }
+
+ _context = FFmpegApi.avcodec_alloc_context3(_codec);
+ if (_context == null)
+ {
+ Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated.");
+
+ return;
+ }
+
+ if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0)
+ {
+ Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened.");
+
+ return;
+ }
+
+ _packet = FFmpegApi.av_packet_alloc();
+ if (_packet == null)
+ {
+ Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated.");
+
+ return;
+ }
+
+ 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<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback);
+ }
+ // libavcodec 59.x changed AvCodec private API layout.
+ else if (avCodecMajorVersion == 59)
+ {
+ _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec501>*)_codec)->Decode);
+ }
+ // libavcodec 58.x and lower
+ else
+ {
+ _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
+ }
+ }
+
+ static FFmpegContext()
+ {
+ _logFunc = Log;
+
+ // Redirect log output.
+ FFmpegApi.av_log_set_level(AVLog.MaxOffset);
+ FFmpegApi.av_log_set_callback(_logFunc);
+ }
+
+ private static void Log(void* ptr, AVLog level, string format, byte* vl)
+ {
+ if (level > FFmpegApi.av_log_get_level())
+ {
+ return;
+ }
+
+ int lineSize = 1024;
+ byte* lineBuffer = stackalloc byte[lineSize];
+ int printPrefix = 1;
+
+ FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix);
+
+ string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim();
+
+ switch (level)
+ {
+ case AVLog.Panic:
+ case AVLog.Fatal:
+ case AVLog.Error:
+ Logger.Error?.Print(LogClass.FFmpeg, line);
+ break;
+ case AVLog.Warning:
+ Logger.Warning?.Print(LogClass.FFmpeg, line);
+ break;
+ case AVLog.Info:
+ Logger.Info?.Print(LogClass.FFmpeg, line);
+ break;
+ 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<byte> bitstream)
+ {
+ FFmpegApi.av_frame_unref(output.Frame);
+
+ int result;
+ int gotFrame;
+
+ fixed (byte* ptr = bitstream)
+ {
+ _packet->Data = ptr;
+ _packet->Size = bitstream.Length;
+ result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
+ }
+
+ if (gotFrame == 0)
+ {
+ 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;
+ 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->HasBFrames = 0;
+ }
+
+ FFmpegApi.av_packet_unref(_packet);
+
+ if (gotFrame == 0)
+ {
+ FFmpegApi.av_frame_unref(output.Frame);
+
+ return -1;
+ }
+
+ return result < 0 ? result : 0;
+ }
+
+ public void Dispose()
+ {
+ fixed (AVPacket** ppPacket = &_packet)
+ {
+ FFmpegApi.av_packet_free(ppPacket);
+ }
+
+ FFmpegApi.avcodec_close(_context);
+
+ fixed (AVCodecContext** ppContext = &_context)
+ {
+ FFmpegApi.avcodec_free_context(ppContext);
+ }
+ }
+ }
+}