FFmpegContext.cs 5.8 KB

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