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)
- 据根媒体信息找到流索引(av_find_best_stream)并获得解码器AVCodec
- 根据AVCodec生成解码器(AVCodecContext,如果指定硬解还要撇嘴AVCodecContext里硬件解码器hw_device_ctx)
- 配置解码器avcodec_parameters_to_context(根据_pFormatContext->streams[_streamIndex]->codecpar)
- 用解码器打开媒体avcodec_open2
- 轮询都帧:把帧放入解码器(avcodec_send_packet)从解码器里获取解码的帧(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 }
来源:oschina
链接:https://my.oschina.net/u/4333555/blog/4287965