FFMpeg.AutoGen:用官方example代码介绍如何使用FFMpeg.AutoGen(未完成)

时间:2024-04-09 11:21:26

FFMpeg是一套C编译的开源工具集。主要用于视频处理,可以编解码视频,建立流媒体服务器等等。官方网站:http://ffmpeg.org/

FFMpeg.AutoGen封装方法以方便C#调用FFmpeg。项目地址:https://github.com/Ruslan-B/FFmpeg.AutoGen。可以使用NuGet安装。

本文使用ffmpeg.autogen版本4.2.2,对应ffmpeg版本也是4.2.2。

FFMpeg.AutoGen:用官方example代码介绍如何使用FFMpeg.AutoGen(未完成)

AutoGen只是封装调用FFmpeg,程序还是需要下在FFmpeg工具放在程序目录里,且版本要对应。 笔者用FFMpeg.AutoGetn的官方example代码介绍一下FFMpege如何使用(源代码在其github库里)。example是一个命令行程序,mian函数里面的代码如下。我将通过此函数调用顺序介绍ffmpeg.AutoGet的用法。

  1 private static void Main(string[] args)
  2         {
  3             Console.WriteLine("Current directory: " + Environment.CurrentDirectory);
  4             Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32");
  5 
  6             FFmpegBinariesHelper.RegisterFFmpegBinaries();
  7 
  8             Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}");
  9 
 10             //配置ffmpeg输出日志
 11             SetupLogging();
 12             //配置硬件解码器
 13             ConfigureHWDecoder(out var deviceType);
 14 
 15             //解码
 16             Console.WriteLine("Decoding...");
 17             DecodeAllFramesToImages(deviceType);
 18 
 19             //编码
 20             Console.WriteLine("Encoding...");
 21             EncodeImagesToH264();
 22         }

1.注册FFmpeg库。实际就将ffmpeg库的地址告诉autogen

  1 FFmpegBinariesHelper.RegisterFFmpegBinaries();

注册FFmpeg,这里的FFmpegBinariesHelper类需要在程序里重写。我这里摘抄官方demo的代码

  1 namespace FFmpeg.AutoGen.Example
  2 {
  3     public class FFmpegBinariesHelper
  4     {
  5         internal static void RegisterFFmpegBinaries()
  6         {
  7             var current = Environment.CurrentDirectory;
  8             var probe = Path.Combine("FFmpeg", "bin", Environment.Is64BitProcess ? "x64" : "x86");
  9             while (current != null)
 10             {
 11                 var ffmpegBinaryPath = Path.Combine(current, probe);
 12                 if (Directory.Exists(ffmpegBinaryPath))
 13                 {
 14                     Console.WriteLine($"FFmpeg binaries found in: {ffmpegBinaryPath}");
 15                     ffmpeg.RootPath = ffmpegBinaryPath;
 16                     return;
 17                 }
 18 
 19                 current = Directory.GetParent(current)?.FullName;
 20             }
 21         }
 22     }
 23 }

代码的功能就是寻找ffmpeg的路径。

核心代码:

  1 ffmpeg.RootPath = ffmpegBinaryPath;

2.ffmpeg 一些调用其的配置(可选)

2.1 配置日志输出

  1  	    /// <summary>
  2         /// 配置日志
  3         /// </summary>
  4         private static unsafe void SetupLogging()
  5         {
  6             ffmpeg.av_log_set_level(ffmpeg.AV_LOG_VERBOSE);
  7 
  8             // do not convert to local function
  9             av_log_set_callback_callback logCallback = (p0, level, format, vl) =>
 10             {
 11                 if (level > ffmpeg.av_log_get_level()) return;
 12 
 13                 var lineSize = 1024;
 14                 var lineBuffer = stackalloc byte[lineSize];
 15                 var printPrefix = 1;
 16                 ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix);
 17                 var line = Marshal.PtrToStringAnsi((IntPtr) lineBuffer);
 18                 Console.ForegroundColor = ConsoleColor.Yellow;
 19                 Console.Write(line);
 20                 Console.ResetColor();
 21             };
 22 
 23             ffmpeg.av_log_set_callback(logCallback);
 24         }

主要就是配置日志回调。

核心代码:

  1 ffmpeg.av_log_set_callback(logCallback)

2.2配置硬件解码器ffmpeg是支持硬解的.具体支持类型可以参考ffmpeg官方文档。转载网友摘录的ffmpeg支持硬解编码的枚举。

  1 enum AVHWDeviceType {
  2     AV_HWDEVICE_TYPE_NONE,
  3     AV_HWDEVICE_TYPE_VDPAU,
  4     AV_HWDEVICE_TYPE_CUDA,
  5     AV_HWDEVICE_TYPE_VAAPI,
  6      AV_HWDEVICE_TYPE_DXVA2,
  7      AV_HWDEVICE_TYPE_QSV,
  8      AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
  9      AV_HWDEVICE_TYPE_D3D11VA,
 10      AV_HWDEVICE_TYPE_DRM,
 11      AV_HWDEVICE_TYPE_OPENCL,
 12      AV_HWDEVICE_TYPE_MEDIACODEC,
 13  };
example通过 ConfigureHWDecoder(out var deviceType); 获取系统支持的硬件的解码类型,并让在命令行让用户选择。然后将用户选择的硬件解码器保存到局部变量deviceType里,解码时根据此变量解码。
  1         /// <summary>
  2         /// 配置硬件解码器
  3         /// </summary>
  4         /// <param name="HWtype"></param>
  5         private static void ConfigureHWDecoder(out AVHWDeviceType HWtype)
  6         {
  7             HWtype = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE;
  8             Console.WriteLine("Use hardware acceleration for decoding?[n]");
  9             var key = Console.ReadLine();
 10             var availableHWDecoders = new Dictionary<int, AVHWDeviceType>();
 11             if (key == "y")
 12             {
 13                 Console.WriteLine("Select hardware decoder:");
 14                 var type = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE;
 15                 var number = 0;
 16                 while ((type = ffmpeg.av_hwdevice_iterate_types(type)) != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
 17                 {
 18                     Console.WriteLine($"{++number}. {type}");
 19                     availableHWDecoders.Add(number, type);
 20                 }
 21                 if (availableHWDecoders.Count == 0)
 22                 {
 23                     Console.WriteLine("Your system have no hardware decoders.");
 24                     HWtype = 。;
 25                     return;
 26                 }
 27                 int decoderNumber = availableHWDecoders.SingleOrDefault(t => t.Value == AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2).Key;
 28                 if (decoderNumber == 0)
 29                     decoderNumber = availableHWDecoders.First().Key;
 30                 Console.WriteLine($"Selected [{decoderNumber}]");
 31                 int.TryParse(Console.ReadLine(),out var inputDecoderNumber);
 32                 availableHWDecoders.TryGetValue(inputDecoderNumber == 0 ? decoderNumber: inputDecoderNumber, out HWtype);
 33             }
 34         }
 35 

核心代码:ffmpeg.av_hwdevice_iterate_types(type)获得系统支持的硬件解码。

ffmpeg.av_hwdevice_iterate_types(type)根据传入的硬件解码其类型,返回AVHWDeviceType枚举里下一个系统支持的硬件解码器类型。

3.解码

  1         /// <summary>
  2         /// 解码
  3         /// </summary>
  4         /// <param name="HWDevice"></param>
  5         private static unsafe void DecodeAllFramesToImages(AVHWDeviceType HWDevice)
  6         {
  7             // decode all frames from url, please not it might local resorce, e.g. string url = "../../sample_mpeg4.mp4";
  8             var url = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; // be advised this file holds 1440 frames
  9             using (var vsd = new VideoStreamDecoder(url,HWDevice))
 10             {
 11                 Console.WriteLine($"codec name: {vsd.CodecName}");
 12 
 13                 var info = vsd.GetContextInfo();
 14                 info.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}"));
 15 
 16                 var sourceSize = vsd.FrameSize;
 17                 var sourcePixelFormat = HWDevice == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE ? vsd.PixelFormat : GetHWPixelFormat(HWDevice);
 18                 var destinationSize = sourceSize;
 19                 var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
 20                 using (var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat))
 21                 {
 22                     var frameNumber = 0;
 23                     while (vsd.TryDecodeNextFrame(out var frame))
 24                     {
 25                         var convertedFrame = vfc.Convert(frame);
 26 
 27                         using (var bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], PixelFormat.Format24bppRgb, (IntPtr) convertedFrame.data[0]))
 28                             bitmap.Save($"frame.{frameNumber:D8}.jpg", ImageFormat.Jpeg);
 29 
 30                         Console.WriteLine($"frame: {frameNumber}");
 31                         frameNumber++;
 32                     }
 33                 }
 34             }
 35         }

example源代码里解码主要使用VideoStreamDecoder和VideoFrameConverter两个类。这两个类不是FFMpeg.AutoGen里的类型,而是example代码里。也就是说解码工作是需要用户自己封装解码类。图省事可以直接照搬example里的代码。笔者很推荐读一下这两个类的源代码,可以搞清楚ffmpeg的解码流程。大概的流程是:

  1 初始化AVFormatContext(音视频信息数据)——据根据源找到流——根据流配置视频格式——根据流生成解码器codecContext(解码器信息)——


其中有两个概念包和帧需要注意一下,这里转载灰色飘零博客里描述:

AVPacket

用于存储压缩的数据,分别包括有音频压缩数据,视频压缩数据和字幕压缩数据。它通常在解复用操作后存储压缩数据,然后作为输入传给解码器。或者由编码器输出然后传递给复用器。对于视频压缩数据,一个AVPacket通常包括一个视频帧。对于音频压缩数据,可能包括几个压缩的音频帧。

AVFrame

用于存储解码后的音频或者视频数据。AVFrame必须通过av_frame_alloc进行分配,通过av_frame_free释放。

两者之间的关系

av_read_frame得到压缩的数据包AVPacket,一般有三种压缩的数据包(视频、音频和字幕),都用AVPacket表示。

然后调用avcodec_send_packet 和 avcodec_receive_frame对AVPacket进行解码得到AVFrame。

注:从 FFmpeg 3.x 开始,avcodec_decode_video2 就被废弃了,取而代之的是 avcodec_send_packet 和 avcodec_receive_frame。




参考文档:

【1】FFmpeg视频解码硬件加速

【2】FFmpeg开发之PacketQueue中AVPacket和AVFrame关系

【3】ffmpeg+ffserver搭建流媒体服务器

【4】FFmpeg框架的基础知识

【5】FFMPEG-数据结构解释(AVCodecContext,AVStream,AVFormatContext)