FFmpegContext.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.Graphics.Nvdec.FFmpeg.Native; 3 using System; 4 using System.Runtime.InteropServices; 5 6 namespace Ryujinx.Graphics.Nvdec.FFmpeg 7 { 8 unsafe class FFmpegContext : IDisposable 9 { 10 private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt); 11 12 private readonly AVCodec_decode _decodeFrame; 13 private static readonly FFmpegApi.av_log_set_callback_callback _logFunc; 14 private readonly AVCodec* _codec; 15 private readonly AVPacket* _packet; 16 private readonly AVCodecContext* _context; 17 18 public FFmpegContext(AVCodecID codecId) 19 { 20 _codec = FFmpegApi.avcodec_find_decoder(codecId); 21 if (_codec == null) 22 { 23 Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation."); 24 25 return; 26 } 27 28 _context = FFmpegApi.avcodec_alloc_context3(_codec); 29 if (_context == null) 30 { 31 Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated."); 32 33 return; 34 } 35 36 if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0) 37 { 38 Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened."); 39 40 return; 41 } 42 43 _packet = FFmpegApi.av_packet_alloc(); 44 if (_packet == null) 45 { 46 Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated."); 47 48 return; 49 } 50 51 int avCodecRawVersion = FFmpegApi.avcodec_version(); 52 int avCodecMajorVersion = avCodecRawVersion >> 16; 53 int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF; 54 55 // libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union. 56 if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24)) 57 { 58 _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback); 59 } 60 // libavcodec 59.x changed AvCodec private API layout. 61 else if (avCodecMajorVersion == 59) 62 { 63 _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec501>*)_codec)->Decode); 64 } 65 // libavcodec 58.x and lower 66 else 67 { 68 _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode); 69 } 70 } 71 72 static FFmpegContext() 73 { 74 _logFunc = Log; 75 76 // Redirect log output. 77 FFmpegApi.av_log_set_level(AVLog.MaxOffset); 78 FFmpegApi.av_log_set_callback(_logFunc); 79 } 80 81 private static void Log(void* ptr, AVLog level, string format, byte* vl) 82 { 83 if (level > FFmpegApi.av_log_get_level()) 84 { 85 return; 86 } 87 88 int lineSize = 1024; 89 byte* lineBuffer = stackalloc byte[lineSize]; 90 int printPrefix = 1; 91 92 FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix); 93 94 string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim(); 95 96 switch (level) 97 { 98 case AVLog.Panic: 99 case AVLog.Fatal: 100 case AVLog.Error: 101 Logger.Error?.Print(LogClass.FFmpeg, line); 102 break; 103 case AVLog.Warning: 104 Logger.Warning?.Print(LogClass.FFmpeg, line); 105 break; 106 case AVLog.Info: 107 Logger.Info?.Print(LogClass.FFmpeg, line); 108 break; 109 case AVLog.Verbose: 110 case AVLog.Debug: 111 Logger.Debug?.Print(LogClass.FFmpeg, line); 112 break; 113 case AVLog.Trace: 114 Logger.Trace?.Print(LogClass.FFmpeg, line); 115 break; 116 } 117 } 118 119 public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream) 120 { 121 FFmpegApi.av_frame_unref(output.Frame); 122 123 int result; 124 int gotFrame; 125 126 fixed (byte* ptr = bitstream) 127 { 128 _packet->Data = ptr; 129 _packet->Size = bitstream.Length; 130 result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); 131 } 132 133 if (gotFrame == 0) 134 { 135 FFmpegApi.av_frame_unref(output.Frame); 136 137 // If the frame was not delivered, it was probably delayed. 138 // Get the next delayed frame by passing a 0 length packet. 139 _packet->Data = null; 140 _packet->Size = 0; 141 result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); 142 143 // We need to set B frames to 0 as we already consumed all delayed frames. 144 // This prevents the decoder from trying to return a delayed frame next time. 145 _context->HasBFrames = 0; 146 } 147 148 FFmpegApi.av_packet_unref(_packet); 149 150 if (gotFrame == 0) 151 { 152 FFmpegApi.av_frame_unref(output.Frame); 153 154 return -1; 155 } 156 157 return result < 0 ? result : 0; 158 } 159 160 public void Dispose() 161 { 162 fixed (AVPacket** ppPacket = &_packet) 163 { 164 FFmpegApi.av_packet_free(ppPacket); 165 } 166 167 _ = FFmpegApi.avcodec_close(_context); 168 169 fixed (AVCodecContext** ppContext = &_context) 170 { 171 FFmpegApi.avcodec_free_context(ppContext); 172 } 173 } 174 } 175 }