FFmpegContext.cs 5.7 KB

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