FFMpeg.AutoGen(1)讲解官方example代码:Main函数、 解码

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

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


image

AutoGen只是封装调用FFmpeg,程序还是需要下在FFmpeg工具放在程序目录里,且版本要对应。 笔者用FFMpeg.AutoGetn的官方example代码介绍一下FFMpege如何使用(源代码在其github库里)。

example是一个命令行程序,mian函数里面的代码如下。我将通过此函数调用顺序介绍ffmpeg.AutoGet的用法。

目录:

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

2.ffmpeg 一些调用其的配置(可选)
2.1 配置日志输出
2.2配置硬件解码器ffmpeg是支持硬解的.具体支持类型可以参考ffmpeg官方文档。转载网友摘录的ffmpeg支持硬解编码的枚举。

3.解码函数DecodeAllFramesToImages
3.1 VideoStreamDecoder类
3.2 VideoFrameConverter类
3.3 相关数据结构AVPacket,AVFrame



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

  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.Example里的解码函数DecodeAllFramesToImages

  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的解码流程。

3.1 example里的VideoStreamDecoder类

VideoStreamDecoder作用:通过配置解码器获取实际有用的帧数据,大概的流程是:

  1. 打开流并参数返回格式上下文AVFormatContext (avformat_open_input(&pFormatContext, url, null, null))
  2. 获取媒体信息数据存到AVFormatContext 格式上下文(avformat_find_stream_info(_pFormatContext, null))
  3. 据根AVFormatContext和媒体信息(AVMediaType.AVMEDIA_TYPE_VIDEO )找到最佳匹配的流索引并参数返回解码器AVCodec(av_find_best_stream(_pFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO  , -1, -1 , &codec, 0))
  4. 根据AVCodec分配解码器上下文AVCodecContext(_pCodecContext=avcodec_alloc_context3(codec),如果指定硬解还要配置AVCodecContext里硬件解码器hw_device_ctx)
  5. 配置解码器上下文格式参数,avcodec_parameters_to_context(根据_pFormatContext->streams[_streamIndex]->codecpar)
  6. 根据解码器codec初始化解码器上下文 avcodec_open2(_pCodecContext, codec, null)
  7. 轮询帧:把未解码帧包(AVPacket)放入解码器(avcodec_send_packet)从解码器里获取解码的帧(AVFrame)(avcodec_receive_frame)

3.2example里的VideoFrameConverter类

VideoFrameConverter作用:对帧数据进行规格形状转换,格式转换的大概流程

  • 创建帧格式转换器SwsContext(ffmpeg.sws_getContext,可以指定转换器的算法,具体可以看参考文档【6】)
  • 计算转换过程中需要的缓存
  • 创建缓存:创建缓存指针(ref _dstData, ref _dstLinesize)——创建缓存实际内存——两者关联(av_image_fill_arrays),具体可以看参考文档【7】【8】
  • 轮询转换:实际上就是调用sws_scale,最终返回一个转换好的AVFrame

3.3.相关数据结构AVPacket,AVFrame

其中有两个概念包和帧需要注意一下,这里转载灰色飘零博客里描述(参考文档【5】):

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)

【6】ffmpeg中的sws_scale算法性能测试 一片云雾 2011-10-29

【7】av_image_fill_arrays详解 韭菜大葱馅鸡蛋 2019-12-14

【8】FFmpeg av_image_fill_arrays填充AVFrame数据缓冲 fengyuzaitu 2019-11-12


附件1 Example中unsafe void DecodeAllFramesToImages(AVHWDeviceType HWDevice)解码函数源码及注释

  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 
 10             //使用自行编写的视频解码器类进行解码
 11             using (var vsd = new VideoStreamDecoder(url,HWDevice))
 12             {
 13                 Console.WriteLine($"codec name: {vsd.CodecName}");
 14 
 15                 //获取媒体信息
 16                 var info = vsd.GetContextInfo();
 17                 info.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}"));
 18 
 19                 var sourceSize = vsd.FrameSize;
 20                 //资源编码格式
 21                 var sourcePixelFormat = HWDevice == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE ? vsd.PixelFormat : GetHWPixelFormat(HWDevice);
 22                 //目标尺寸与原尺寸一致
 23                 var destinationSize = sourceSize;
 24                 //目标媒体格式是bit类型
 25                 var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
 26                 //帧格式转换
 27                 using (var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat))
 28                 {
 29                     var frameNumber = 0;
 30                     while (vsd.TryDecodeNextFrame(out var frame))
 31                     {
 32                         var convertedFrame = vfc.Convert(frame);
 33 
 34                         using (var bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], PixelFormat.Format24bppRgb, (IntPtr) convertedFrame.data[0]))
 35                             bitmap.Save($"frame.{frameNumber:D8}.jpg", ImageFormat.Jpeg);
 36 
 37                         Console.WriteLine($"frame: {frameNumber}");
 38                         frameNumber++;
 39                     }
 40                 }
 41             }
 42         }
 43 
View Code

附件2 Example中解码类VideoStreamDecoder类源码及注释

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Drawing;
  4 using System.IO;
  5 using System.Runtime.InteropServices;
  6 
  7 namespace FFmpeg.AutoGen.Example
  8 {
  9     public sealed unsafe class VideoStreamDecoder : IDisposable
 10     {
 11         private readonly AVCodecContext* _pCodecContext;
 12         private readonly AVFormatContext* _pFormatContext;
 13         private readonly int _streamIndex;
 14         //
 15         private readonly AVFrame* _pFrame;
 16         //
 17         private readonly AVFrame* _receivedFrame;
 18         private readonly AVPacket* _pPacket;
 19         /// <summary>
 20         /// 视频解码器
 21         /// </summary>
 22         /// <param name="url">视频流URL</param>
 23         /// <param name="HWDeviceType">硬件解码器类型(默认AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)</param>
 24         public VideoStreamDecoder(string url, AVHWDeviceType HWDeviceType = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
 25         {
 26             //分配一个AVFormatContext
 27             _pFormatContext = ffmpeg.avformat_alloc_context();
 28             //分配一个AVFrame
 29             _receivedFrame = ffmpeg.av_frame_alloc();
 30 
 31             var pFormatContext = _pFormatContext;
 32             //将源音视频流传递给ffmpeg即ffmpeg打开源视频流
 33             ffmpeg.avformat_open_input(&pFormatContext, url, null, null).ThrowExceptionIfError();
 34             //获取音视频流信息
 35             ffmpeg.avformat_find_stream_info(_pFormatContext, null).ThrowExceptionIfError();
 36             AVCodec* codec = null;
 37             //在源里找到最佳的流,如果指定了解码器,则根据解码器寻找流,将解码器传递给codec
 38             _streamIndex = ffmpeg.av_find_best_stream(_pFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0).ThrowExceptionIfError();
 39             //根据解码器分配一个AVCodecContext ,仅仅分配工具,还没有初始化。
 40             _pCodecContext = ffmpeg.avcodec_alloc_context3(codec);
 41             //如果硬解码
 42             if (HWDeviceType != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
 43             {
 44                 //根据硬件编码类型创建AVHWDeviceContext,存在AVFormatContext.hw_device_ctx (_pCodecContext->hw_device_ctx)
 45                 ffmpeg.av_hwdevice_ctx_create(&_pCodecContext->hw_device_ctx, HWDeviceType, null, null, 0).ThrowExceptionIfError();
 46             }
 47             //将最佳流的格式参数传递给codecContext
 48             ffmpeg.avcodec_parameters_to_context(_pCodecContext, _pFormatContext->streams[_streamIndex]->codecpar).ThrowExceptionIfError();
 49             //根据codec初始化pCodecContext 。与_pCodecContext = ffmpeg.avcodec_alloc_context3(codec);对应
 50             ffmpeg.avcodec_open2(_pCodecContext, codec, null).ThrowExceptionIfError();
 51 
 52             CodecName = ffmpeg.avcodec_get_name(codec->id);
 53             FrameSize = new Size(_pCodecContext->width, _pCodecContext->height);
 54             PixelFormat = _pCodecContext->pix_fmt;
 55             //分配AVPacket
 56             /* AVPacket用于存储压缩的数据,分别包括有音频压缩数据,视频压缩数据和字幕压缩数据。
 57                        它通常在解复用操作后存储压缩数据,然后作为输入传给解码器。或者由编码器输出然后传递给复用器。
 58                        对于视频压缩数据,一个AVPacket通常包括一个视频帧。对于音频压缩数据,可能包括几个压缩的音频帧。
 59              */
 60             _pPacket = ffmpeg.av_packet_alloc();
 61 
 62             //分配AVFrame
 63             /*AVFrame用于存储解码后的音频或者视频数据。
 64                     AVFrame必须通过av_frame_alloc进行分配,通过av_frame_free释放。
 65             */
 66             _pFrame = ffmpeg.av_frame_alloc();
 67         }
 68 
 69         public string CodecName { get; }
 70         public Size FrameSize { get; }
 71         public AVPixelFormat PixelFormat { get; }
 72 
 73         public void Dispose()
 74         {
 75             ffmpeg.av_frame_unref(_pFrame);
 76             ffmpeg.av_free(_pFrame);
 77 
 78             ffmpeg.av_packet_unref(_pPacket);
 79             ffmpeg.av_free(_pPacket);
 80 
 81             ffmpeg.avcodec_close(_pCodecContext);
 82             var pFormatContext = _pFormatContext;
 83             ffmpeg.avformat_close_input(&pFormatContext);
 84         }
 85 
 86         /// <summary>
 87         /// 解码下一帧帧
 88         /// </summary>
 89         /// <param name="frame">参数返回解码后的帧</param>
 90         /// <returns></returns>
 91         public bool TryDecodeNextFrame(out AVFrame frame)
 92         {
 93             //取消帧的引用。帧将不会被任何资源引用
 94             ffmpeg.av_frame_unref(_pFrame);
 95             ffmpeg.av_frame_unref(_receivedFrame);
 96             int error;
 97             do
 98             {
 99 
100 
101                 try
102                 {
103                     #region 读取帧忽略无效帧
104                     do
105                     {
106 
107                         //读取无效帧
108                         error = ffmpeg.av_read_frame(_pFormatContext, _pPacket);//根据pFormatContext读取帧,返回到Packet中
109                         if (error == ffmpeg.AVERROR_EOF)//如果已经是影视片流末尾则返回
110                         {
111                             frame = *_pFrame;
112                             return false;
113                         }
114                         //数值是负数是错误信息
115                         error.ThrowExceptionIfError();
116                     } while (_pPacket->stream_index != _streamIndex); //忽略掉音视频流里面与有效流(初始化(构造函数)时标记的_streamIndex)不一致的流
117                     #endregion
118 
119                     //将帧数据放入解码器
120                     ffmpeg.avcodec_send_packet(_pCodecContext, _pPacket).ThrowExceptionIfError();  //将原始数据数据(_pPacket)作为输入提供给解码器(_pCodecContext)
121                 }
122                 finally
123                 {
124                     //消除对_pPacket的引用
125                     ffmpeg.av_packet_unref(_pPacket);
126                 }
127 
128 
129 
130                 //读取解码器里解码(_pCodecContext)后的帧通过参数返回(_pFrame)
131                 error = ffmpeg.avcodec_receive_frame(_pCodecContext, _pFrame);
132 
133             } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));//当返回值等于 EAGAIN(再试一次),认为读取帧结束
134             error.ThrowExceptionIfError();
135 
136             if (_pCodecContext->hw_device_ctx != null)//如果配置了硬件解码则调用硬件解码器解码
137             {
138                 //将_pFrame通过硬件解码后放入_receivedFrame
139                 ffmpeg.av_hwframe_transfer_data(_receivedFrame, _pFrame, 0).ThrowExceptionIfError();
140                 frame = *_receivedFrame;
141             }
142             else
143             {
144                 frame = *_pFrame;
145             }
146             return true;
147         }
148 
149         /// <summary>
150         /// 获取媒体TAG信息
151         /// </summary>
152         /// <returns></returns>
153         public IReadOnlyDictionary<string, string> GetContextInfo()
154         {
155             AVDictionaryEntry* tag = null;
156             var result = new Dictionary<string, string>();
157             while ((tag = ffmpeg.av_dict_get(_pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
158             {
159                 var key = Marshal.PtrToStringAnsi((IntPtr) tag->key);
160                 var value = Marshal.PtrToStringAnsi((IntPtr) tag->value);
161                 result.Add(key, value);
162             }
163 
164             return result;
165         }
166     }
167 }
View Code

附件3 Example中帧转换类VideoFrameConverter类源码及注释

  1 using System;
  2 using System.Drawing;
  3 using System.Runtime.InteropServices;
  4 
  5 namespace FFmpeg.AutoGen.Example
  6 {
  7     public sealed unsafe class VideoFrameConverter : IDisposable
  8     {
  9         private readonly IntPtr _convertedFrameBufferPtr;
 10         private readonly Size _destinationSize;
 11         private readonly byte_ptrArray4 _dstData;
 12         private readonly int_array4 _dstLinesize;
 13         private readonly SwsContext* _pConvertContext;
 14         /// <summary>
 15         /// 帧格式转换
 16         /// </summary>
 17         /// <param name="sourceSize"></param>
 18         /// <param name="sourcePixelFormat"></param>
 19         /// <param name="destinationSize"></param>
 20         /// <param name="destinationPixelFormat"></param>
 21         public VideoFrameConverter(Size sourceSize, AVPixelFormat sourcePixelFormat,
 22             Size destinationSize, AVPixelFormat destinationPixelFormat)
 23         {
 24             _destinationSize = destinationSize;
 25                 //分配并返回一个SwsContext。您需要它使用sws_scale()执行伸缩/转换操作
 26                 //主要就是使用SwsContext进行转换!!!
 27             _pConvertContext = ffmpeg.sws_getContext(sourceSize.Width, sourceSize.Height, sourcePixelFormat,
 28                 destinationSize.Width,
 29                 destinationSize.Height
 30                 , destinationPixelFormat,
 31                 ffmpeg.SWS_FAST_BILINEAR //默认算法 还有其他算法
 32                 , null
 33                 , null
 34                 , null //额外参数 在flasgs指定的算法,而使用的参数。如果  SWS_BICUBIC  SWS_GAUSS  SWS_LANCZOS这些算法。  这里没有使用
 35                 );
 36             if (_pConvertContext == null) throw new ApplicationException("Could not initialize the conversion context.");
 37             //获取媒体帧所需要的大小
 38             var convertedFrameBufferSize = ffmpeg.av_image_get_buffer_size(destinationPixelFormat
 39                 , destinationSize.Width, destinationSize.Height
 40                 , 1);
 41             //申请非托管内存,unsafe代码
 42             _convertedFrameBufferPtr = Marshal.AllocHGlobal(convertedFrameBufferSize);
 43 
 44             //转换帧的内存指针
 45             _dstData = new byte_ptrArray4();
 46             _dstLinesize = new int_array4();
 47 
 48             //挂在帧数据的内存区把_dstData里存的的指针指向_convertedFrameBufferPtr
 49             ffmpeg.av_image_fill_arrays(ref _dstData, ref _dstLinesize
 50                 , (byte*) _convertedFrameBufferPtr
 51                 , destinationPixelFormat
 52                 , destinationSize.Width, destinationSize.Height
 53                 , 1);
 54         }
 55 
 56         public void Dispose()
 57         {
 58             Marshal.FreeHGlobal(_convertedFrameBufferPtr);
 59             ffmpeg.sws_freeContext(_pConvertContext);
 60         }
 61 
 62         public AVFrame Convert(AVFrame sourceFrame)
 63         {
 64             //转换格式
 65             ffmpeg.sws_scale(_pConvertContext
 66                 , sourceFrame.data
 67                 , sourceFrame.linesize
 68                 , 0, sourceFrame.height
 69                 , _dstData, _dstLinesize);
 70 
 71             var data = new byte_ptrArray8();
 72             data.UpdateFrom(_dstData);
 73             var linesize = new int_array8();
 74             linesize.UpdateFrom(_dstLinesize);
 75 
 76             return new AVFrame
 77             {
 78                 data = data,
 79                 linesize = linesize,
 80                 width = _destinationSize.Width,
 81                 height = _destinationSize.Height
 82             };
 83         }
 84     }
 85 }
View Code
posted @ 2020-05-05 21:39  伊一线天  阅读(7300)  评论(9编辑  收藏  举报