FFMpeg是一套C编译的开源工具集。主要用于视频处理,可以编解码视频,建立流媒体服务器等等。官方网站:http://ffmpeg.org/
FFMpeg.AutoGen封装方法以方便C#调用FFmpeg。项目地址:https://github.com/Ruslan-B/FFmpeg.AutoGen。可以使用NuGet安装。
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 };
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作用:通过配置解码器获取实际有用的帧数据,大概的流程是:
- 打开流并参数返回格式上下文AVFormatContext (avformat_open_input(&pFormatContext, url, null, null))
- 获取媒体信息数据存到AVFormatContext 格式上下文(avformat_find_stream_info(_pFormatContext, null))
- 据根AVFormatContext和媒体信息(AVMediaType.AVMEDIA_TYPE_VIDEO )找到最佳匹配的流索引并参数返回解码器AVCodec(av_find_best_stream(_pFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO , -1, -1 , &codec, 0))
- 根据AVCodec分配解码器上下文AVCodecContext(_pCodecContext=avcodec_alloc_context3(codec),如果指定硬解还要配置AVCodecContext里硬件解码器hw_device_ctx)
- 配置解码器上下文格式参数,avcodec_parameters_to_context(根据_pFormatContext->streams[_streamIndex]->codecpar)
- 根据解码器codec初始化解码器上下文 avcodec_open2(_pCodecContext, codec, null)
- 轮询帧:把未解码帧包(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。
参考文档:
【2】FFmpeg开发之PacketQueue中AVPacket和AVFrame关系
【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
附件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 }
附件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 }