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

DirectX.Capture 类库的音频文件保存

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (14投票s)

2006年3月15日

CPOL

12分钟阅读

viewsIcon

203728

downloadIcon

2596

增强 DirectX.Capture 类以捕获音频到 WMA 文件。

引言

最初,DirectX.Capture 只支持 AVI 文件保存。这种格式并不太实用,因为它通常会产生巨大的文件。有时,只需要保存音频(例如,FM 收音机或电视声音)。本文介绍的增强功能使得能够以 Windows Media Audio 格式保存音频。我花了很多时间搜索和阅读,才找到了本文提出的解决方案。此外,我没有找到其他关于此主题的完整 C# 示例,因此我认为关于此主题的文章可能会很有趣。

考虑因素

我的第一个想法是将音频保存为 WAV(每分钟约 10 MB)或 MP3(每分钟约 1 MB),并描述由此产生的实现。这些通常是已知格式,但编码并非一帆风顺。在查看可能的编码示例时,我偶然发现了 Windows Media 格式。将这些示例与 DirectX.Capture 类示例进行比较,我注意到需要进行从小的到大的代码修改。

我倾向于使用 SetOutputFileName()RenderStream() 的较小版本。我注意到还使用了 ConfigureFilterUsingProfileGuid()。对于 ConfigureFilterUsingProfileGuid() 的作用,我并不完全清楚。它用于更改 Windows Media 音频/视频格式,但示例显示的是一个仅视频的 GUID,我感觉还有更多可能的选择。

SetOutputFileName() 方法非常重要。它不仅设置文件名,顾名思义,还会将过滤器添加到图中。对于 ASF,这将是 ASF 文件写入器;对于 AVI,这将是 AVI 混合器和文件写入器(两个过滤器!)。选择首选的 Windows Media 音频/视频格式至关重要,这一点显而易见。因此,在开始实现保存 Windows Media 文件时,提出了许多问题。

需要使用哪个文件写入器(查看 graphedt)?WM ASF 文件写入器似乎是那个。应该使用哪个音频/视频格式?嗯,我不知道。如何更改音频/视频格式?这可以通过 ASF 文件写入器对应的属性窗口完成。通过在 graphedt 中右键单击过滤器,会显示窗口,所以我知道它在那里。还有其他更改音频/视频格式的方法吗?通过代表音频/视频格式的配置文件。可能有哪些格式?我不知道。最终的视频分辨率是多少?我不知道。

当我开始编码时,我收到了一些奇怪的代码错误。换句话说:我不得不做更多研究来找到这些问题的答案。

IWMProfile 接口

我们应该从哪里开始?首先,请访问 MSDN。对于音频捕获,请在此处搜索有趣的文章“创建音频捕获图”。您可以在那里找到很多信息!此外,MSDN 还有一个特殊的 Windows Media Format SDK 页面;只需搜索“Windows Media Format SDK”。在那里,我找到了 DirectX 默认提供的 GUID,所以这已经是我想要的答案之一了。

选择所需的音频/视频文件格式的最佳方法似乎是 IWMProfile 接口。实际上,选择是通过加载代表音频/视频文件格式的配置文件来完成的。我注意到 MSDN 还提到 ConfigureFilterUsingProfileGuid()(没有注释)不应再使用。这让我有点惊讶,因为在 DirectShowLib 中,我发现有一个注释说明该接口已过时。

我发现了一个有趣的示例,它向我展示了获取音频/视频格式列表的可能性:Idael Cardoso 的 Windows Media Audio Compressor。这篇文章描述了如何使用 IWMProfile,并且示例程序向我展示了一个带有 Windows Media Audio 格式列表的漂亮的 ListBox。它帮助我找到了更多信息:Idael Cardoso 的 C# Windows Media Format SDK 翻译

然后我下定决心,想先让它工作起来,其余的就随之而来。

ASF 文件写入器的问题

当我开始编码时,ASF 文件写入器出现了问题。出于某种原因,在使用 mediaControl.Run() 时发生了错误。我的通用做法是提前添加过滤器,以便在过滤器实际使用之前即可访问其属性窗口。在实际捕获时,添加的过滤器将被连接(使用 RenderStream() 或直接连接)。我使用了该方法来保存 Wav、MP3 和 AVI 文件,但现在这导致了 ASF(+ WMA、WMV)的问题。

为什么要调用 mediaControl.Run()?嗯,在菜单中选择某些内容时,会调用 updateMenu()... 我认为这些问题与过滤器本身有关。我注意到我看到的所有示例要么在 SetOutputFileName() 之后立即配置了过滤器,要么根本没有配置。所以,我选择了第一种解决方案。对于音频来说,这还可以,因为默认选择已经足够好。对于视频来说,这不可接受。

当前实现

在说明解决方案之前,我想解释一下当前将音频/视频捕获到文件的实现。此功能列在此处,可以在 Capture.csrenderGraph 函数中找到。在代码中,首先设置 mediaSubtype。然后调用 SetOutputFileName() 来添加 AVI 复用器和文件写入器。此外,还会存储文件名。下一步是初始化视频渲染。会考虑一个可能的压缩器过滤器。之后,初始化音频渲染。在 StartPreviewIfNeeded() 函数中调用 mediaControl.Run() 时,捕获图开始。

//Original code fragment renderGraph Capture.cs
Guid mediaSubType = MediaSubType.Avi;
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, 
     Filename, out muxFilter, out fileWriterFilter );
if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );

// Render video (video -> mux)
if ( VideoDevice != null )
{
    // Try interleaved first, because if the device supports it,
    // it's the only way to get audio as well as video
    cat = PinCategory.Capture;
    med = MediaType.Interleaved;
    hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
         videoDeviceFilter, videoCompressorFilter, muxFilter ); 
    if( hr < 0 ) 
    {
        med = MediaType.Video;
        hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
             videoDeviceFilter, videoCompressorFilter, muxFilter ); 
        if ( hr == -2147220969 )
            throw new DeviceInUseException( "Video device", hr );
        if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );
    }
}

// Render audio (audio -> mux)
if ( AudioDevice != null )
{
    cat = PinCategory.Capture;
    med = MediaType.Audio;
    hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
         audioDeviceFilter, audioCompressorFilter, muxFilter );
    if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );
}

isCaptureRendered = true;
didSomething = true;

增强功能

我选择展示启用 Windows Media 文件保存的代码,并使用 ConfigureFilterUsingProfileGuid() 来选择适当的音频/视频格式。它通过与特定音频/视频格式对应的 GUID 来实现,因此它对应于特定的配置文件。另一个优点是,通过这种方式,代码将更容易理解。

在未来的主题中,可以更详细地解释 IWMProfile 接口。目前,将不解释此内容,因为它会使文章稍长。所以,如果本文能激发您的好奇心,我认为这是非常积极的。

我还选择仅显示对 Capture.cs(在 DirectX.Capture 中)所做的更改,而不显示对 CaptureTest.cs 的更改。除非您想在音频和视频格式之间进行选择,或者想在 AVI 或 Windows Media ASF 格式(如 WMA 或 WMV)之间切换,否则对 CaptureTest.cs 的更改并非真正需要。此类选择可以通过菜单选项或按钮按下来实现。我认为,如果确实需要用户控制功能,自己添加它是很好的学习目标。好了,现在来看增强功能。

录制文件模式

声明了枚举 RecFileModeType,它指定了可能的音频/视频录制文件选择。选择包括 AVI、WMV 和 WMA。WMA 选择成为默认值,因为本示例是关于音频文件保存的。添加了 AVI 选择,因此原始功能仍然存在,如有需要可以使用。WMV 不在本文章中讨论。弄清楚如何使用它是一个很好的学习目标。此外,WMV 的使用稍微复杂一些,因为视频可以是视频+音频流或仅视频流。一个好的实现应该提前检查是否可能发生冲突。为此,可以使用 IWMProfile 接口,因为 IWMProfile 可以提供有关其使用的流的信息。

变量 recFileMode 包含音频/视频录制文件模式,因此此变量获得值 RecFileModeType.Wma。变量 recFileMode 应通过 RecFileMode 访问。添加了一个检查,以防止在文件捕获期间更改文件模式。这是为了防止奇怪的副作用。它还表明在更改 recFileMode 值时可以执行其他功能。一个不错的功能可能是能够更改文件名扩展名。此代码应放在 Capture.cs 中,最好是在开头,因为在那里可以找到更多声明。

/// <summary>
/// Recording file mode type enumerations
/// </summary>

public enum RecFileModeType
{
    /// <summary> Avi video (+ audio) </summary>
    Avi,
    /// <summary> Wmv video (+ audio) </summary>
    Wmv,
    /// <summary> Wma audio </summary>
    Wma,
}

private RecFileModeType recFileMode = RecFileModeType.Wma;

/// <summary>
/// Recording file modes
/// </summary>

public RecFileModeType RecFileMode
{
    get { return(recFileMode); }
    set
    {
        if(this.graphState == GraphState.Capturing)
        {
            // Value may not be changed now
            return;
        }
        recFileMode = value;
    }
}

捕获,RenderStream()

最有趣的部分是对捕获特定代码中 RenderStream() 的修改,如前所述。主要区别在于考虑了文件录制模式。

保存 WMA 文件时的注意事项

  • 无音频压缩器
  • 文件名带有扩展名 *.wma(或 *.asf
  • RecFileMode = RecFileModeType.Wma

保存 WMV 文件时的注意事项

  • 无视频压缩器
  • 无音频压缩器
  • 文件名带有扩展名 *.wmv(或 *.asf
  • 某些视频格式没有音频流;在当前实现中,视频捕获将失败
  • RecFileMode = RecFileModeType.Wmv

保存 AVI 文件时的注意事项

  • 视频压缩器,例如 DV AVI?
  • 文件名带有扩展名 *.avi
  • RecFileMode = RecFileModeType.Avi

代码应该能自解释(我希望)。添加了检查,因此根据文件格式,可以执行特定操作。第一个操作是初始化 mediaSubtype。下一个操作是配置 ASF 文件写入器。配置是一行代码。有趣的是,文件写入器指针的类型转换是为了调用 ConfigureFilterUsingProfileGuid()。此解决方案特定于 .NET;它为更改首选音频/视频配置文件提供了简单的解决方案。

还有一个问题留给您:WMProfile_V80_64StereoAudio 是什么意思?WMProfile_V80_64StereoAudio 是我选择的默认音频录制格式。还有更多可能的选择,将在稍后讨论。对于不同的选择,必须使用不同的值。此外,如果您想保存视频,只需选择一个有效的 Windows Media 视频格式。音频和视频渲染部分看起来相同,但有一个主要区别。进入视频渲染部分时,会检查文件格式:对于音频捕获,不得渲染视频,因此必须忽略该部分!

// Record captured audio/video in Avi, Wmv or Wma format
Guid mediaSubType; // Media sub type

// Set media sub type
if(RecFileMode == RecFileModeType.Avi)
{
    mediaSubType = MediaSubType.Avi;
}
else
{
    mediaSubType = MediaSubType.Asf;
}

// Initialize the Avi or Asf file writer
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, 
     Filename, out muxFilter, out fileWriterFilter );
if( hr < 0 )
{
    Marshal.ThrowExceptionForHR( hr );
}

// For Wma (and Wmv) a suitable profile
// must be selected. This can be done
// via a property window, however the muxFilter
// is just created. if needed, the
// property Windows should show up right now!
// Another solution is to configure
// the Asf file writer, however DShowNet does not
// have the interface that is needed. Please ensure it is added. 
if(RecFileMode == RecFileModeType.Wma)
{
    IConfigAsfWriter lConfig = muxFilter as IConfigAsfWriter;

    // Obsolete interface?
    // According to MSDN no, according to DirectShowLib yes.
    // For simplicity, it will be used ...
    hr = 
        lConfig.ConfigureFilterUsingProfileGuid(WMProfile_V80_64StereoAudio);
    if(hr < 0)
    {
        // Problems with selecting video write format
        // Release resources ... (not done yet)
        Marshal.ThrowExceptionForHR( hr );
    }
}

// Render video (video -> mux) if needed or possible
if((VideoDevice != null)&&(RecFileMode != RecFileModeType.Wma))
{
    // Try interleaved first, because if the device supports it,
    // it's the only way to get audio as well as video
    cat = PinCategory.Capture;
    med = MediaType.Interleaved;
    hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
         videoDeviceFilter, videoCompressorFilter, muxFilter ); 
    if( hr < 0 ) 
    {
        med = MediaType.Video;
        hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
             videoDeviceFilter, videoCompressorFilter, muxFilter ); 
        if ( hr == -2147220969 )
        {
            throw new DeviceInUseException( "Video device", hr );
        }
        if( hr < 0 )
        {
            Marshal.ThrowExceptionForHR( hr );
        }
    }
}

// Render audio (audio -> mux) if possible
if ( AudioDevice != null )
{
    // Keep in mind that for certain Wmv
    // formats do not have an audio stream,
    // so when using this code, please ensure
    // you use a format which supports
    // audio!
    cat = PinCategory.Capture;
    med = MediaType.Audio;
    hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
         audioDeviceFilter, audioCompressorFilter, muxFilter );
    if( hr < 0 )
    {
         Marshal.ThrowExceptionForHR( hr );
    }
}

isCaptureRendered = true;
didSomething = true;

WMA 音频格式

需要调用 ConfigureFilterUsingProfileGuid() 函数来设置适当的音频格式。在此示例中,通过 Guid 提供了七种特定的 Windows Media 音频格式(硬编码)。每个 Guid 代表一种特殊的音频格式和配置文件。还有更多 Windows Media 格式。但是,这些格式支持视频(和/或音频)。获取这些格式的最佳方法是使用 IWMProfile 功能。然后,您将确信您能够获取真正存在的格式。

在将来的代码示例中,可以展示 IWMProfile 方法。目前,这已足够。感谢 IWMProfile 接口,我能够检索属于特定音频格式和配置文件的所有名称。以下代码(至少包含您将要使用的音频格式)应放在 Capture.cs 的某个位置。

// Windows Media Audio 8 for Dial-up Modem (Mono, 28.8 Kbps)
private static readonly Guid WMProfile_V80_288MonoAudio = 
             new Guid("7EA3126D-E1BA-4716-89AF-F65CEE0C0C67");

// Windows Media Audio 8 for Dial-up Modem 
// (FM Radio Stereo, 28.8 Kbps)
private static readonly Guid WMProfile_V80_288StereoAudio = 
             new Guid("7E4CAB5C-35DC-45bb-A7C0-19B28070D0CC");

// Windows Media Audio 8 for Dial-up Modem (32 Kbps)
private static readonly Guid WMProfile_V80_32StereoAudio = 
             new Guid("60907F9F-B352-47e5-B210-0EF1F47E9F9D");

// Windows Media Audio 8 for Dial-up Modem 
// (Near CD quality, 48 Kbps)
private static readonly Guid WMProfile_V80_48StereoAudio = 
             new Guid("5EE06BE5-492B-480a-8A8F-12F373ECF9D4");

// Windows Media Audio 8 for Dial-up Modem (CD quality, 64 Kbps)
private static readonly Guid WMProfile_V80_64StereoAudio = 
             new Guid("09BB5BC4-3176-457f-8DD6-3CD919123E2D");

// Windows Media Audio 8 for ISDN (Better than CD quality, 96 Kbps)
private static readonly Guid WMProfile_V80_96StereoAudio = 
             new Guid("1FC81930-61F2-436f-9D33-349F2A1C0F10");

// Windows Media Audio 8 for ISDN (Better than CD quality, 128 Kbps)
private static readonly Guid WMProfile_V80_128StereoAudio = 
             new Guid("407B9450-8BDC-4ee5-88B8-6F527BD941F2");

IConfigAsWriter 接口

为了使代码正常工作,仍然需要添加一个接口,因为 DShowNET 不支持 IConfigAsfWriter 的接口。DirectShowLib 支持此接口。因此,如果您使用该库,则无需额外工作。此接口可以添加到 Capture.cs 或其他合适的位置。请记住相应地更改命名。

[Guid("45086030-F7E4-486a-B504-826BB5792A3B"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IConfigAsfWriter
{
    /// Obsolete?
    [PreserveSig]
    int ConfigureFilterUsingProfileId([In] int dwProfileId);

    /// Obsolete?
    [PreserveSig] 
    int GetCurrentProfileId([Out] out int pdwProfileId);

    /// Obsolete?
    [PreserveSig]
    int ConfigureFilterUsingProfileGuid([In, 
        MarshalAs(UnmanagedType.LPStruct)] Guid guidProfile);

    [PreserveSig]
    int GetCurrentProfileGuid([Out] out Guid pProfileGuid);

    /// Obsolete?
    [PreserveSig]
    int ConfigureFilterUsingProfile([In] IntPtr pProfile);

    /// Obsolete?
    [PreserveSig]
    int GetCurrentProfile([Out] out IntPtr ppProfile);

    [PreserveSig]
    int SetIndexMode([In, 
          MarshalAs(UnmanagedType.Bool)] bool bIndexFile);

    [PreserveSig]
    int GetIndexMode([Out, 
          MarshalAs(UnmanagedType.Bool)] out bool pbIndexFile);
}

音频渲染,测试

本文所示的修改已经过测试。这并不意味着它不会出现任何问题。在测试过程中,我注意到我没有听到声音。因此,我必须进行一项额外的修改。进行此修改是因为我使用的是 Hauppauge PVR150-MCE (Amity2) 电视卡。这张卡通过 PCI 总线获取音频,而不是通过有线连接。

当前的 DirectX.Capture 类将与使用有线连接到声卡的电视卡一起工作。因此,对于那些拥有特殊电视卡的人来说,以下修改可能很有趣:在预览期间,进行音频渲染。我通过变量 audioviapci 添加了一个选项,以告知程序使用音频渲染或不执行任何操作。

  • 对于有线音频连接,必须使用值 false(默认选择)。
  • 当音频通过 PCI 总线传输时,必须使用值 true

变量 audioviapci 应放在 Capture.cs 的某个位置。有一个限制:电视卡驱动程序必须有一个音频捕获设备。如果电视卡驱动程序没有此类设备,那么以下代码也将不起作用。如何处理此类情况可以作为新文章的主题。对于捕获音频,此修改不是必需的。它仅用于收听。

// Option for selection audio rendering via the PCI bus of the TV card
// For wired audio connections the value must be false!
// For TV-cards, like the Hauppauge PVR150, the value must be true!
// This TV-card does not have a wired audio connection. However, this
// option will work only if the TV-card driver has an audio device!
private bool audioviapci = false;

实际代码应放在 Capture.csrenderGraph() 函数中。此代码应插入在 renderGraph() 中开始视频渲染的检查 if (wantPreviewRendered && !isPreviewRendered) 之后。

// Special option to enable rendering audio via PCI bus
if(audioviapci)
{
    med = MediaType.Audio;
    hr = captureGraphBuilder.RenderStream( ref cat, ref med, 
                            audioDeviceFilter, null, null ); 
    if( hr < 0 )
    {
        Marshal.ThrowExceptionForHR( hr );
    }
}

RenderStream() 中的最后一个参数是 null。这意味着音频被渲染到默认音频渲染器。可能仍然没有声音。在这种情况下,请执行以下操作:

  • 转到“设备”中的“音频设备”菜单,然后选择正确的音频设备。
  • 转到“选项”中的“属性页”菜单,然后单击“视频交叉开关”。转到显示“视频解码器输出”的 ListBox,然后选择“音频解码器输出”。然后转到显示“视频调谐器输入”的另一个列表框,然后选择“调谐器音频输入”。然后勾选“链接相关流”复选框,然后单击“确定”。

关注点

没有添加演示程序。请从 DirectX.Capture 类库页面下载完整源代码,并下载演示版本。可下载的源代码文件包含代码更改和原始代码。只需用我的版本替换原始文件。

通常,我使用条件来使原始代码和新代码在同一个文件中。如果代码有错误,这很有用。然后,我将该代码与前一个版本进行比较。我删除了条件,因为这个源代码文件应该易于使用。我并没有完全删除条件,所以您应该能够找到代码更改的确切位置。

我发布了文章 DirectShow - C# 中使用 IKsPropertySet 进行电视微调DirectX.Capture 类库的 Windows Media Video 格式文件保存,其中包含一个代码示例,该示例包含了本文提到的代码更改。此外,该代码示例还有更多功能,例如通过视频设备过滤器捕获音频、添加电视微调、FM 收音机和去隔行。此外,该代码示例通过条件 DSHOWNET 支持 DShowNETDirectShowLib 接口库。请看一下这些文章。

反馈和改进

我希望这段代码能帮助您理解 DirectX.Capture 类的结构。我也希望我提供了一个可能对您有用的增强功能。欢迎随时发表评论和提问。

历史

  • 2006 年 3 月 15 日 -- 发布原始版本
  • 2007 年 8 月 15 日 -- 更新了文章和下载
  • 2007 年 8 月 29 日 -- 更新了文章
  • 2008 年 2 月 23 日 -- 更新了链接
DirectX.Capture 类库的音频文件保存 - CodeProject - 代码之家
© . All rights reserved.