使用 C# 从电影文件中提取静态图片






4.81/5 (38投票s)
2005年2月24日
4分钟阅读

621912

23890
一个可以从大多数电影文件格式中提取静态图片的示例应用程序。
引言
在 Windows Media Player 中播放电影文件时按下“Print Screen”键将**无法**保存当前帧。本示例应用程序可以将帧保存为 JPEG 文件。它可以从大多数电影文件格式中提取帧,包括 *.wm? (Windows Media Player)、*.avi、*.mpeg、*.mov (QuickTime) 和 *.dat (DivX)。虽然存在提供此功能的商业和共享软件应用程序,但本示例的代码将允许您构建满足您确切需求的应用程序。
背景
在 Windows Media Player 中播放电影文件时按下“Print Screen”键将无法保存当前帧。因此,我开始寻找一种简单的方法来从最常用的媒体应用程序(例如 Windows Media Player、QuickTime 或 DivX Player)播放的电影文件中捕获帧。事实证明这比我最初想象的要复杂。因此,我决定编写一个示例应用程序并将其提交给 CodeProject,因为我经常使用他们的资源;这是一种回馈该网站人员的方式。
Microsoft 处理此问题的方法是使用 DirectShow,但它没有托管的等价物。通过深入研究 DirectX 文档,我发现了 MediaDet
类,该类在一些 VB 示例中使用过。它不是理想的解决方案,但在许多情况下已经足够了。通过将 DirectShow 开发者运行时(包含在 DirectX SDK 的 Extras 中)中的 qedit.dll 封装在托管程序集中,我就可以调用 MediaDetClass::WriteBitmapBits
方法,该方法将电影文件中的帧保存到位图文件中。
使用代码
如前所述,该应用程序的核心是 MediaDetClass
类中的 WriteBitmapBits
方法。为了能够访问此方法,您需要添加一个引用(从 Visual Studio 菜单,如果您使用的是 Visual Studio,则为 Project/Add Reference,或使用 .NET Framework 工具中的 tlbimp)到 qedit.dll。在此应用程序的代码中,我包含了托管程序集(名为 interop.dexterlib.dll,它是在 VS 中创建的)。
声明 MediaDetClass
对象时,您会看到类似这样的代码:
MediaDetClass md = new MediaDetClass();
md.Filename = "sample.mov";
md.CurrentStream = 0;
string fBitmapName = "sample" + ".bmp" ;
md.WriteBitmapBits( 0, 320, 240, fBitmapName );
调用 WriteBitmapBits
后,MediaDetClass
对象会按照 DirectShow 文档的说法,处于“位图抓取模式”。关于这一点,重要的一点是,您需要为加载新电影创建一个新的 MediaDetClass
实例(这有点像该类的属性是“只读”的)。
我将 CurrentStream
属性设置为 0,并且它在我尝试过的所有示例中都有效。此外,似乎只能写入位图文件。因此,为了节省磁盘空间,我添加了将位图文件转换为 JPEG 文件的代码。您需要记住,即使我们有 100GB 的硬盘,如果您想在 30 分钟的电影中每 0.1 秒保存一帧,您也很快就会消耗掉几 GB 的空间。
关注点
“Save”按钮将当前帧保存到“tmp”子目录下的 JPEG 文件中。“Scan”按钮会循环遍历整个电影,每秒(或 0.1 秒)保存一帧。
使用 Visual Studio 导入的 MediaDetClass
不是 Windows 控件,它的基类是 System.Object
,并且似乎在调用 WriteBitmapBits
方法后,对刚刚创建的位图文件的句柄仍保持打开状态。因此,应用程序有时会抱怨文件已被使用。我只是增加了一个 counter
变量来避免大多数这种情况;另一种解决方案是将 MediaDetClass
再次封装在更复杂的实现中。
如前所述,这不是一个理想的解决方案。DirectShow 不会以托管版本出现(与 DirectX API 的其余部分相反)。我认为这是出于性能原因。但正如本应用程序所示,互操作版本可以胜任工作;正如他们所说,“它就是能用!”,这在许多情况下都很好。
我包含了一个文件(“build.cmd”),其中包含在 C# 编译器可在路径中找到的 shell 窗口中编译应用程序所需的命令行。
局限性和已知问题
默认情况下,该应用程序每秒提取一帧。在“Options”菜单下,您可以选择每十分之一秒提取一帧。这仅在您按下“Scan”按钮时才生效。
该应用程序仅保存分辨率为 320 x 240 的位图。它们保存在“tmp”子目录中(该目录将在您运行应用程序时创建)。
该应用程序旨在从电影文件中提取图片,而不是用于观看电影。创建 MediaDetClass
类型的新实例使得这种限制是不可避免的。
我创建了一个线程来跟踪扫描整个电影时的进度(显示在一个静态标签上)。该线程仅更新标签。
打开第二个电影时,电影的长度会立即被当前位置覆盖。