65.9K
CodeProject 正在变化。 阅读更多。
Home

调用 FFmpeg 导出帧

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (11投票s)

2015 年 4 月 6 日

LGPL3

9分钟阅读

viewsIcon

51699

downloadIcon

2437

直接在 C# 中使用 ffmpeg DLL 提取帧并创建缩略图

抱歉,为了包含所有必需的文件,我使用 7-Zip 将它们压缩并放入两个 RAR 归档部分,以遵守每个文件 10MB 的限制(仅使用 RAR 时,我最终得到五个文件)和文件扩展名 - 您可以使用 7-Zip 或 WinRAR 来提取两者
 
 
要使用以下内容,请将 FFmpeg 文件 avcodec-56.dllavdevice-56.dllavfilter-5.dllavformat-56.dllavutil-54.dllpostproc-53.dllswresample-1.dllswscale-3.dll 添加到项目“BjSTools.Media.FFmpeg”中的“x86”和“x64”文件夹(32 位和 64 位版本)中,然后再打开项目解决方案
 

引言

我之前发表了一篇名为“Another FFmpeg.exe C# Wrapper”的文章,通过命令行参数使用 FFmpeg.exe 来提取帧图像。但这种方法并不能让我满意,因为它一点都不干净。所以我开始研究直接在 .NET 中通过 PInvoke 调用 FFmpeg DLL 的可能性,在尝试了许多无效的项目后,我找到了 FFmpeg.AutoGen,它从一开始就可以配合示例工作。

解决方案包含两部分:一个 .NET 库,用于映射 FFmpeg DLL 并使用它们从多媒体文件中提取帧图像;一个应用程序,用于使用该库轻松创建缩略图。

这个项目的核心基于 Ruslan-B 在 GitHub 上的 FFmpeg.AutoGen 包装器(https://github.com/Ruslan-B/FFmpeg.AutoGen)。我做了一些修改,主要是为了支持元数据提取,否则我将直接导入该项目。

为了了解查找多媒体文件信息、搜索视频流和提取帧图像所需的类和函数,我使用了 FFmpeg 文档:https://ffmpeg.net.cn/doxygen/trunk/

使用应用程序

该应用程序可用于从多媒体文件创建缩略图。您可以定义缩略图的布局。以下参数可以设置

  • 视频帧的列数和行数
  • 单个帧的宽度(像素)
  • 整个缩略图周围的边距(像素)
  • 帧之间的间距(两次标题和帧之间)像素
  • 背景颜色
  • 标题字体样式和颜色
  • 索引(时间位置)字体样式、文本和阴影颜色
  • 帧的边框颜色以及是否绘制边框

还有一些选项可以配置应用程序应如何运行

  • 当通过命令行传递的作业完成时自动关闭应用程序
  • 自动选择文件名(与电影文件相同,但扩展名不同)
  • 图像格式 Bitmap、GIF、JPEG、PNG 或 TIFF(如果选择 JPEG,则包括质量)
  • 使用精确时间位置或仅使用关键帧

有两种方法可以设置这些选项:使用命令行参数或配置文件。

配置文件是标准的 MS INI 文件。您可以通过应用程序的“设置”对话框或使用文本编辑器来编辑或创建标准配置文件(文件名与应用程序相同,但扩展名为 .ini 而非 .exe)。选项必须位于“SheetOptions”部分。以下选项可用

选项 描述
OutputFormat 输出格式(bmpgifjpgpngtif
JpegQuality 如果输出格式为 jpg,则输出质量(0-100
AutoOutputFilename 自动选择输出文件名,它将与多媒体文件相同,只是图像扩展名不同(0:禁用,1:启用)
AutoClose 通过命令行参数传递的作业完成后自动关闭应用程序(0:禁用,1:启用)
ThumbColumns 缩略图列数
ThumbRows 缩略图行数
ThumbWidth 缩略图宽度(像素)
Margin 边距(像素)
填充 缩略图之间的间距(像素)
BackgroundColor 背景颜色(HEX 格式 - 例如,使用 RGB 的 #443322 或使用 ARGB 的 #88443322
HeaderColor 标题文本颜色(HEX 格式 - 例如,使用 RGB 的 #FFFFFF 或使用 ARGB 的 #88FFFFFF
IndexColor 索引文本颜色(HEX 格式 - 例如,使用 RGB 的 #FFFF88 或使用 ARGB 的 #88FFFF88
IndexShadowColor 索引阴影颜色(HEX 格式 - 例如,使用 RGB 的 #000000 或使用 ARGB 的 #88000000
ThumbBorderColor 帧边框颜色(HEX 格式 - 例如,使用 RGB 的 #000000 或使用 ARGB 的 #88000000
DrawThumbnailBorder 为每个帧绘制边框(0:禁用,1:启用)
ForceExactTimePosition 为每个帧使用精确的时间位置(0:禁用/仅关键帧,1:启用)
HeaderFont 标题文本的字体样式(格式:style|size|name
  • style:BoldItalicRegularStrikeoutUnderline
  • size:单位为 pt,例如 10.8
  • name:monospacesans-serifserif 或字体名称(例如 Arial
IndexFont 索引文本的字体样式(格式:style|size|name
  • style:BoldItalicRegularStrikeoutUnderline
  • size:单位为 pt,例如 10.8
  • name:monospacesans-serifserif 或字体名称(例如 Arial

这些是命令行参数,它们将覆盖配置文件中的设置

参数 描述
文件 多媒体输入文件
/? 显示一个包含可能命令行参数的对话框
/A=0|1 启用(1)或禁用(0)自动输出文件名
/F=0|1 启用(1)或禁用(0)使用精确时间位置(速度较慢但更准确)
/C=N 将缩略图列数设置为 N
/R=N 将缩略图行数设置为 N
/W=N 将缩略图宽度设置为 N 像素
/M=N 将边距设置为 N 像素
/P=N 将缩略图间距设置为 N 像素
/X=0|1 作业完成后启用(1)或禁用(0)自动退出
/I=N 设置输出格式 N:0=BMP, 1=GIF, 2=JPG, 3=PNG, 4=TIF
/O=file 定义手动输出文件 - 与输入文件顺序相同,如果输入文件多于输出文件,则为每个不完整的对使用“另存为”对话框或自动文件名
/V=file 覆盖此配置文件中的标准选项

可以通过启动应用程序然后使用 **Open** 按钮选择一个或多个多媒体文件,或者通过将多媒体文件 **拖放** 到应用程序窗体上来创建缩略图,也可以通过命令行参数传递多媒体文件

ThumbSheetCreator.exe /I=0 /C=6 /R=2 /X=1 /F=1 /A=1 C:\MyMovie.mp4

使用代码

主类是 FFmpegMediaInfo ,如果您打算使用此代码,应该查看它。您应该使用想要使用的多媒体文件对其进行初始化。然后它将收集有关该文件的基本信息。然后可以使用信息和提取方法。不要忘记在使用完该类后将其释放 - 或者简单地使用“using”

// Load the multimedia file
using (FFmpegMediaInfo info = new FFmpegMediaInfo(@"C:\Videos\Test.mp4"))
{
    // Get the duration
    TimeSpan d = info.Duration;
    string duration = String.Format("{0}:{1:00}:{2:00}", d.Hours, d.Minutes, d.Second);

    // Get the video resolution
    Size s = info.VideoResolution;
    string resolution = String.Format("{0}x{1}", s.Width, s.Height);
    
    // Get the first video stream information
    FFmpegStreamInfo vs = info.Streams
        .FirstOrDefault(v => v.StreamType == FFmpegStreamType.Video);
    
    // If the video stream  exists, extract two random frames
    List<Bitmap> imgs = new List<Bitmap>();
    if (vs != null)
    {
        // Prepare random timestamps
        Random rnd = new Random();
        long dTicks = info.Duration.Ticks;
        TimeSpan t1 = new TimeSpan(Convert.ToInt64(dTicks * rnd.NextDouble()));
        TimeSpan t2 = new TimeSpan(Convert.ToInt64(dTicks * rnd.NextDouble()));
        
        // Extract images
        imgs = info.GetFrames(
            info.Streams.IndexOf(vs), // stream index
            new List<TimeSpan>() { t1, t2 }, // time positions of the frames
            true, // force exact time positions; not previous keyframes only
            (index, count) =>
            {
                // Get the progress percentage
                double percent = Convert.ToDouble(index) / Convert.ToDouble(count) * 100.0;
                
                return false; // Not canceling the extraction
            }
        );
    }

    // Extract a standard 6x5 frame thumbnail sheet from the default video stream
    Bitmap thumb = info.GetThumbnailSheet(
        -1, // Default video stream, will throw Exception if there is none
        new VideoThumbSheetOptions(6, 5), // preset sheet options with 6 columns and 5 rows
        (index, count) =>
        {
            // Get the progress percentage
            double percent = Convert.ToDouble(index) / Convert.ToDouble(count) * 100.0;
            
            return false; // Not canceling the extraction
        }
    );
}

FFmpegMediaInfo - 基本方法

如果您需要更多功能,可以随时编辑 FFmpegMediaInfo 类 - 它并不意味着是完整的!有关如何使用 FFmpeg 类和函数的信息可以在 FFmpeg 文档 中找到。调用是实例安全的 - 意味着您可以同时使用多个应用程序实例;但我不确定同一实例中的线程,因为我还没有尝试过。代码会自动使用 32 位或 64 位版本的 FFmpeg,具体取决于选择的平台类型 - 即使选择了“any platform”,对于合适的系统也会使用 64 位版本。因此,两种版本都应始终提供!

首先,这是代码的基本方法:当加载文件时(调用的函数是 OpenFileOrUrl),FFmpeg 的所需部分将被初始化。之后,文件将被 FFmpeg 加载,并将其信息存储到 AVFormatContext 实例中。这个类已经包含了 FFmpegMediaInfo 提取的大部分信息。 nb_streams 字段的 AVFormatContext 包含有关不同流的信息,类型为 AVStream,这些流稍后在提取图像时会用到。AVFormatContext 和 AVStream 的元数据都存储为 AVDictionary 类型,其中一些函数需要将其转换为 Dictionary<string, string> 类型。信息以及 AVFormatContextAVStream 实例都存储在 FFmpegMediaInfo 实例中。

该类专门用于查找时间位置。为了避免遍历到指定时间位置的每一帧,在查找时会选择该时间位置之前的一个关键帧。要搜索流,将使用函数 av_seek_frame(),并将 AVStream 的 skip_to_keyframe 设置为 1,并将标志 AVSEEK_FLAG_BACKWARD 设置为 1。然后(如果启用了 ForceExactTimePosition),将解码后续帧,直到流中的实际时间位置在时间基(每帧时间)内,或者再次偏离查找的时间位置。在查找过程中,必须将 AVFormatContext 的 seek2any 字段设置为 0 - 否则查找很可能会在两个关键帧之间结束,并且解码到下一个关键帧会导致图像损坏,因为中间的帧依赖于先前的关键帧。要提取帧图像,首先必须使用函数 av_read_frame() 加载所选视频流的下一个包,然后必须使用函数 avcodec_decode_video2() 解码该包,使用函数 sws_scale() 查找图像参数,然后可以将所有内容传递给 .NET Bitmap 类来加载图像。要确定解码帧的时间位置,可以使用函数 av_frame_get_best_effort_timestamp()

关注点

使用 FFmpeg 和 PInvoke 时最烦人的一点是,如果出现错误,很难找到。我只能鼓励您多尝试和调试 - Visual Studio 会在您中断而未释放时负责释放已分配的内存。

使用关键帧模式存在一个我尚未解决甚至尚未找到的主要 bug:缩略图使用较快的关键帧方法创建时,前几张图片似乎总是相同的,并且时间戳与目标时间相差很多。

未来计划

说实话,我没有太多时间来处理这个项目,所以不要期望很快会有更新!不过,这是我目前想到的

  • 修复使用关键帧模式时的相同图片 bug
  • 实现一个类似枚举器的可搜索帧提供程序

法律

本文适用的 GNU Lesser General Public License v3 (LGPL3) 适用于 FFmpeg.AutoGenFFmpeg 以及我编写的代码。对于 FFmpeg,许可证设置为 LGPL2.1 或更高版本。

 

© . All rights reserved.