• 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
  • 相关阅读:
    追随我心
    开心孕期创业经验和教训总结
    记和老友李吃饭
    如何理解hashCode的作用:
    周计划(2014.08.05~2014.08.10)
    个人职业提升内容
    个人职业发展分析和实现方法
    go 实现的排序算法
    xxx go内置函数
    6.并发
  • 原文地址:https://www.cnblogs.com/edzjx/p/12832994.html
Copyright © 2020-2023  润新知