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

在 C 和 C# 中创建录音机

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (13投票s)

2010 年 3 月 23 日

CPL

16分钟阅读

viewsIcon

142443

downloadIcon

606

了解如何使用 C 和 C# 录制音频和播放声音文件。

目录 

本文内容

  • 目录
  • 概述
  • 引言
  • 发送 MCI 命令
  • Wait、Notify 和 Test 标志
  • 处理 MCI 错误
  • 从输入设备录制
    • 打开设备
    • 开始录制
    • 暂停录制
    • 恢复录制
    • 停止录制
    • 获取缓冲区长度
    • 保存录制的缓冲区
    • 关闭设备
  • 播放声音文件
    • 加载文件
    • 播放文件
    • 暂停
    • 恢复
    • 获取当前位置
    • 获取文件长度
    • 跳转到指定位置
    • 关闭设备
    • 简而言之
    • MCI 和 .NET
    • 创建托管签名
    • 接收 MCI_MMNOTIFY
  • 示例代码

概述

本文将重点介绍如何使用 C 和 C# 中的 MCI (Media Control Interface) 从输入设备录制声音以及如何播放声音文件。

本文不涉及 MCI 的讨论,甚至不进行介绍。相反,它提供了关于我们将需要使用哪些技术来录制和播放声音文件的技术性讨论。如果您需要 MCI 的介绍,请参阅 MSDN 文档。

我们将首先讨论完成任务所需的类型和函数。然后,我们将研究如何在 C 或 C# 应用程序中利用这些类型和函数。

本文中的演示示例将使用 C 语言。在最后一节,我们将介绍 .NET 和 C#。此外,文章附带了由 C 和 C# 编写的示例应用程序。

引言

实际上,录制和播放声音文件的方式有很多种。然而,在本文中,我们将重点关注 MCI (Media Control Interface),因为它是控制所有媒体类型的高级易用接口之一。

您可以通过发送类似 Windows 消息的命令或通过发送字符串消息来使用 MCI 控制多媒体硬件。后一种方法在脚本语言中更可取。由于我们专注于 C 和 C# 语言,因此我们将只考虑第一种方法。

由于 .NET Framework 不包含处理 MCI(或任何多媒体)的类,因此我们需要深入研究 Windows SDK,特别是位于系统目录中的 WinMM.dll (Windows Multimedia Library)。

要从 C# 应用程序访问此库,您需要创建自己的封送类型和 PInvoke (Platform Invocation) 方法,以便您的托管代码能够与非托管服务器进行通信。这就是本文最后一节的内容。

要从 C 应用程序访问此库,只需在项目中引用库对象文件 WinMM.lib。请注意,与 MCI 相关的所有函数和结构都以 mci. 为前缀。

在接下来的几节中,我们将讨论在深入编码之前需要了解的函数和结构。

发送 MCI 命令

使用 MCI 进行 Windows 多媒体编程的关键函数是 mciSendCommand()。此函数向 MCI 发送命令。如前所述,您可以通过两种方法之一来编程 MCI:您可以发送类似 Windows 消息的命令,也可以向 MCI 发送字符串消息。我们将用于发送 MCI 命令的关键函数是 mciSendCommand()。要发送字符串消息,您可以使用 mciSendMessage() 函数,本文未涵盖此函数。

mciSendCommand() 函数的定义如下

MCIERROR mciSendCommand(
  MCIDEVICEID IDDevice,
  UINT        uMsg,
  DWORD       fdwCommand,
  DWORD_PTR   dwParam
);

此函数接收四个输入参数

  • IDDevice
    接收命令的设备的 ID。例如,录制时输入设备的 ID,播放时输出设备的 ID。正如您所知,PC 可以连接许多设备。您可以忽略此参数,只需将函数传递 0 即可将消息定向到默认设备(在控制面板的“声音”小程序中选择)。
  • uMsg
    要发送的消息(命令)。常见的消息将在接下来的几节中介绍。
  • fdwCommand
    消息的标志(选项)。每条消息都有其自身的选项。但是,所有消息都共享 MCI_WAIT、MCI_NOTIFY 和 MCI_TEST 标志(稍后介绍)。
  • dwParam
    一个结构,包含命令消息的特定参数。

因此,每个命令(消息)都有其名称、标志和结构参数。

如果成功,此函数返回 0,否则返回错误代码。

Wait、Notify 和 Test 标志

所有 MCI 消息的通用标志是 MCI_WAIT、MCI_NOTIFY 和 MCI_TEST。

Wait 标志 MCI_WAIT 指示消息应同步处理;意味着函数在消息处理完成之前不会返回。例如,如果您发送带有 MCI_WAIT 的播放消息,您的应用程序将在整个文件完成之前暂停。因此,不应将 MCI_WAIT 用于播放或录制消息。

Notify 标志 MCI_NOTIFY 与 Wait 标志相反。它指示消息应异步处理;意味着函数会立即返回,而不会等待消息完成。当消息处理完成时,它会将 MM_MCINOTIFY 消息发送到消息参数的 dwCallback 成员中指定的窗口。此标志应用于播放和录制消息。

MM_MCINOTIFY 消息的 wParam 值通常设置为 MCI_NOTIFY_SUCCESSFUL 以指示命令成功,否则设置为 MCI_NOTIFY_FAILURE。

Test 标志 MCI_TEST 检查设备是否可以处理此消息,它不处理消息。如果设备可以处理该消息,函数将返回 0,否则返回非零。您仅在极少数情况下使用此标志。

请记住,您应该只选择三个标志中的一个,不能组合使用它们。

如果您没有指定这三个标志中的任何一个,调用将被视为异步处理,并且在完成后您不会收到通知。

处理 MCI 错误

每个 MCI 命令都可能成功或失败。如果命令成功,mciSendCommand() 返回 0 (FALSE/false)。否则,它返回错误代码。

MCI 将错误代码定义为以 MCIERR_ 为前缀的常量,例如 MCIERR_DEVICE_NOT_INSTALLED 和 MCIERR_FILE_NOT_FOUND(名称不言自明)。您可以使用 mciGetErrorString() 函数获取错误代码的友好错误消息。此函数定义如下

BOOL mciGetErrorString(
  DWORD fdwError,
  LPTSTR lpszErrorText,
  UINT cchErrorText
);

此函数接受三个参数

  • fdwError
    mciSendCommand() 函数返回的错误代码。
  • lpszErrorText
    一个字符串缓冲区,用于接收错误的描述。
  • cchErrorText
    缓冲区中的字符数。

以下 C 示例显示了在发生错误时如何向用户显示友好错误消息

	TCHAR szBuffer[256];
	DWORD dwErr;

	dwErr = mciSendCommand(/* arguments */);

	mciGetErrorStringW(dwErr, szBuffer, 256);
	// cchErrorText =
	//     sizeof(szBuffer) / sizeof(szBuffer[0])

从输入设备录制

要使用 MCI 从输入设备录制,请遵循以下步骤

  1. 打开输入设备以接收数据。
  2. 命令 MCI 开始录制过程。
  3. 完成后,停止录制过程。
  4. 如果适用,保存录制的缓冲区。
  5. 关闭打开的设备。

请记住,在发送每个命令后都应检查是否发生错误。前面的检索错误消息的方法将非常有用。

打开设备

要打开设备,只需将 open 命令 MCI_OPEN 及其标志和参数传递给 mciSendCommand() 函数。

MCI_OPEN 的参数是 MCI_OPEN_PARMS 结构。此结构包含有关 open 命令的信息。该结构的定义如下

typedef struct {
    DWORD_PTR    dwCallback;
    MCIDEVICEID  wDeviceID;
    LPCSTR       lpstrDeviceType;
    LPCSTR       lpstrElementName;
    LPCSTR       lpstrAlias;
} MCI_OPEN_PARMS;

实际上,在打开设备时,您将只使用该结构的第三个和第四个成员,lpstrDeviceType lpstrElementNamelpstrDeviceType 确定将使用的设备类型(数字音频、数字视频等)。在我们录制和播放声音文件的示例中,我们将此成员设置为“waveaudio”,以指示我们将处理波形(WAV)数据。

另一方面,如果要打开用于录制的输入设备,lpstrElementName 应设置为空字符串(即“”)。如果要播放文件,请将此成员设置为该文件的完整路径。

MCI_OPEN 命令的通用标志是

  • Wait、Notify 和 Test 标志
    Wait 命令通常用于 MCI_OPEN。
  • MCI_OPEN_ELEMENT
    必需。设置了 MCI_OPEN_PARMS 的 lpstrDeviceType。对于 WAV 数据,它设置为“waveaudio”。
  • MCI_OPEN_TYPE
    必需。设置了 MCI_OPEN_PARMS 的 lpstrElementType。如果用于录制,则设置为空字符串;如果要播放文件,则设置为文件路径。

您将始终组合使用 MCI_WAIT、MCI_OPEN_ELEMENT 和 MCI_OPEN_TYPE 标志来执行 MCI_OPEN 命令。

函数返回时,结构中的 wDeviceID 成员将被设置为已打开设备的 ID。您应该保留此 ID,直到使用 close 命令关闭设备为止,以便将来对该设备进行调用。

以下 C 代码演示了如何打开用于录制的输入设备

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

开始录制

打开输入设备后,您可以使用 MCI_RECORD 命令指示 MCI 开始录制过程。此命令需要已打开的输入设备以及类型为 MCI_RECORD_PARMS 的参数。该结构定义如下

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_RECORD_PARMS;

此结构定义的成员如下

  • dwCallback
    如果在命令标志中指定了 MCI_NOTIFY,则命令处理完成后应调用该窗口的窗口句柄。
  • dwFrom
    指示开始录制的缓冲区位置(以千分之一秒为单位)。在大多数情况下,此值设置为零。
  • dwTo
    指示达到该位置时停止录制的缓冲区位置。除非您要录制特定时长,否则此成员应为零。

MCI_RECORD 的通用标志是

  • MCI_WAIT、MCI_NOTIFY 和 MCI_TEST
    通常使用 MCI_NOTIFY 标志。如果是这样,您应该处理 MM_MCINOTIFY 消息。
  • MCI_FROM
    如果使用了参数的 dwFrom 成员,则设置此标志。
  • MCI_TO
    如果使用了参数的 dwTo 成员,则设置此标志。

如果要录制特定时长,请将结构的 dwTo 成员设置为该特定时长(以千分之一秒为单位),并将您的标志与 MCI_TO 组合。当达到此时长时,MCI 会自动停止录制过程,并发送 MM_MCINOTIFY 消息(如果您在标志中设置了 MCI_NOTIFY)。

以下 C 示例演示了如何开始录制

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// We do not need a notification message
	// we will send a Stop command, when
	// we finish.
	mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD)&parmRec);

以下代码演示了如何录制特定时长

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = hWnd;	// window handle
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 30000; // 30 seconds

	// Notify me when you finish the 30 seconds
	mciSendCommand(wDeviceID, MCI_RECORD,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

暂停录制

要暂停录制过程,只需将 MCI_PAUSE 命令发送到 MCI。此命令接受类型为 MCI_GENERIC_PARMS 的参数,该参数定义如下

typedef struct {
    DWORD_PTR dwCallback;
} MCI_GENERIC_PARMS;

此结构仅包含一个成员 dwCallback。正如您所知,如果您在命令标志中指定了 MCI_NOTIFY,MCI 将将 MM_MCINOTIFY 消息发送到此成员中指定的窗口。

MCI_PAUSE 没有特定标志,只有 Wait、Notify 和 Test 标志。

以下 C 示例演示了如何暂停录制过程。请注意,您应该已经在录制中,否则 mciSendCommand() 将返回错误。

	MCI_GENERIC_PARMS parmGen;

	parmGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT, (DWORD)&parmGen);

恢复录制

要从暂停状态恢复,您可以发送 MCI_RESUME 命令到 MCI。此命令与 MCI_PAUSE 命令非常相似。它接受相同的参数和相同的标志。示例相同,只需更改命令名称。

停止录制

录制完成后,您需要停止录制过程。要实现这一点,请将 MCI_STOP 命令与设备 ID 和其参数一起传递给 MCI。

此命令与 MCI_PAUSE 和 MCI_RESUME 相同。它接受相同的标志和相同的参数,示例也相同,只需更改命令名称。

获取缓冲区长度

您录制了多长时间?可以使用 MCI_STATUS 命令轻松回答此问题。此命令查询 MCI 并检索有关当前会话的信息。

MCI_STATUS 接受类型为 MCI_STATUS_PARMS 的结构参数,该参数定义如下

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwReturn;
    DWORD     dwItem;
    DWORD     dwTrack;
} MCI_STATUS_PARMS;

此结构定义以下成员

  • dwCallback
    前面已讨论过。
  • dwReturn
    调用返回后,它应包含查询的返回值。
  • dwItem
    要查询的项目。
  • dwTracks
    轨道长度或数量(特定于某些查询项目)。

MCI_STATUS 接受的通用标志

  • Wait、Notify 和 Test 标志
    通常使用 Wait 标志。
  • MCI_STATUS_ITEM
    在大多数情况下必需。指示已设置结构的 dwItem 成员。

如果您想查询 MCI 的特定信息,请将 MCI_STATUS 命令与 MCI_STATUS_ITEM 和 MCI_WAIT 标志一起传递给 MCI,并将参数结构的 dwItem 字段设置为以下值之一(有些是用于输出设备)

  • MCI_STATUS_LENGTH
    检索缓冲区的总长度(以千分之一秒为单位)。
  • MCI_STATUS_MODE
    检索设备的当前模式,它可以是以下值之一(名称不言自明)

     

    • MCI_MODE_NOT_READY
    • MCI_MODE_PAUSE
    • MCI_MODE_PLAY
    • MCI_MODE_STOP
    • MCI_MODE_RECORD
    • MCI_MODE_SEEK
  • MCI_STATUS_NUMBER_OF_TRACKS
    检索轨道总数。
  • MCI_STATUS_POSITION
    检索当前位置(以千分之一秒为单位)。
  • MCI_STATUS_READY
    如果设备准备就绪,则返回 TRUE(C# 中为 true),否则返回 FALSE(C# 中为 false)。

函数返回时,结构中的 dwReturn 字段应包含所选查询项的结果。

以下 C 示例检索了录制缓冲区的长度

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_LENGTH;
	parmStatus.dwTrack    = 0;

	mciSendCommand(0, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the number of seconds
	// parmStatus.dwReturn / 1000

保存录制的缓冲区

在关闭设备之前,您可以将当前录制的缓冲区保存到音频(波形)文件中。MCI_SAVE 命令指示 MCI 将缓冲区保存到其结构参数中指定的文件。

MCI_SAVE 的参数是 MCI_SAVE_PARMS 结构,定义如下

typedef struct {
    DWORD_PTR  dwCallback;
    LPCTSTR    lpfilename;
} MCI_SAVE_PARMS;

此结构仅包含两个成员,dwCallback(已讨论过)和 lpfilenamelpfilename 指向一个字符串缓冲区,其中包含要保存文件的名称和完整路径。

MCI_SAVE 接受少量标志

  • Wait、Notify 和 Test 标志
    您将始终使用 Wait (MCI_WAIT) 标志。
  • MCI_SAVE_FILE
    必需。您将始终设置此标志。它指示 lpfilename 包含目标文件的路径。

以下示例将录制的缓冲区保存到文件“recording.wav”

	MCI_SAVE_PARMS parmSave;

	parmSave.dwCallback = 0;
	parmSave.lpfilename = TEXT("recording.wav");
	// save to the current directory

	mciSendCommand(wDeviceID, MCI_SAVE,
		MCI_WAIT | MCI_SAVE_FILE, (DWORD)&parmSave);

关闭设备

您在完成工作后必须始终关闭设备,这通过 MCI_CLOSE 命令完成,该命令接受类型为 MCI_GENERIC_PARMS 的参数(已讨论过)。

MCI_CLOSE 只接受 Wait、Notify 和 Test 标志。

以下 C 示例关闭打开的设备

	MCI_GENERIC_PARMS parmsGen;

	parmsGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)&parmsGen);

播放声音文件

牢记上一节学到的内容,将非常有益于我们讨论如何使用 MCI 播放声音文件。

实际上,上一节中应用的规则和命令也适用于此处。

要使用 MCI 播放声音文件,您应遵循以下步骤

  1. 加载音频文件。这会自动打开输出设备。
  2. 命令 MCI 开始播放过程。
  3. 完成后关闭打开的设备。

同样,您应该在每次调用 mciSendCommand() 后检查是否发生错误。

加载文件

正如您所知,要打开多媒体设备,您需要将 MCI_OPEN 命令传递给 MCI。要指定要加载的文件,请在 MCI_OPEN_PARMS 结构的 lpstrElementName 字段中指定文件的完整路径。

以下 C 示例演示了如何打开输出设备并加载要播放的文件

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("recording.wav");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

播放文件

要开始播放当前加载的文件,您可以使用 MCI_PLAY 命令。此命令接受类型为 MCI_PLAY_PARMS 的结构参数,该参数定义如下

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_PLAY_PARMS;

乍一看,您会发现它与 MCI_RECORD_PARMS 相同。实际上,您说得对。此结构定义了与 MCI_RECORD_PARMS 结构相同的成员。此外,这些成员的描述也相同。

dwFrom 指定开始播放的位置(以千分之一秒为单位)。dwTo 则指定播放结束的位置(也以千分之一秒为单位)。如果您设置了 dwFrom,则需要设置 MCI_FROM 标志。反之,如果您设置了 dwTo,则需要设置 MCI_TO 标志。如果您需要从头到尾播放文件,则将这两个成员留空,并且不要指定 MCI_FROM 或 MCI_TO。

您很可能将 MCI_PLAY 标志与 Notify 标志 MCI_NOTIFY 结合使用,以允许代码在文件播放时继续执行。如果您这样做,您的应用程序将收到发送到参数结构 dwCallback 成员中指定的窗口的 MM_MCINOTIFY 消息。

以下 C 示例演示了如何开始播放文件

	MCI_PLAY_PARMS parmPlay;

	// Play the file
	// from the start to the end

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY, (DWORD)&parmRec);

以下代码播放文件的前三分钟

	MCI_PLAY_PARMS parmPlay;

	// play only 3 minutes
	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	// 3 * 60 * 1000
	parmRec.dwTo	   = 180000;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

暂停

正如您所知,您可以使用 MCI_PAUSE 命令暂停播放,该命令接受 MCI_GENERIC_PARMS 参数。

恢复

要从暂停状态恢复播放,您可以使用前面讨论过的 MCI_RESUME 命令。

获取当前位置

要获取文件的当前位置,请将 MCI_STATUS 命令与标志(MCI_WAIT 和 MCI_STATUS_ITEM)及其参数(MCI_STATUS_PARMS)一起传递给 MCI。不要忘记将结构中的 dwItem 成员设置为 MCI_STATUS_POSITION,以便在结构中的 dwReturn 成员中获取当前位置(以千分之一秒为单位)。以下 C 示例对此进行了演示

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_POSITION;
	parmStatus.dwTrack    = 0;

	mciSendCommand(wDeviceID, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the current position
	// parmStatus.dwReturn / 1000

获取文件长度

与获取当前位置类似,您也可以通过相同的方式获取完整文件长度。但是,您需要将项目指定为 MCI_STATUS_LENGTH。

跳转到指定位置

要更改当前位置,请将 MCI_SEEK 命令传递给 MCI。此命令接受类型为 MCI_SEEK_PARMS 的结构参数,该参数定义如下

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwTo;
} MCI_SEEK_PARMS;

此参数定义了以下成员

  • dwCallback
    如果使用 MCI_NOTIFY 标志异步执行调用,则在 MCI_MMNOTIFY 消息通知的窗口。
  • dwTo
    要跳转到的位置(以千分之一秒为单位)。

MCI_SEEK 命令接受以下标志

  • Wait、Notify 和 Test 标志
    您将始终使用 Wait 标志。
  • MCI_SEEK_TO_END
    如果指定,将跳转到文件末尾。
  • MCI_SEEK_TO_START
    如果指定,将跳转到文件开头。
  • MCI_TO
    如果指定,MCI 将使用参数结构中的 dwTo 成员的值。

以下 C 示例跳转到文件开头

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 0;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_SEEK_TO_START, (DWORD)&parmSeek);

以下示例跳转到文件的第三分钟

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 180000;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_TO, (DWORD)&parmSeek);

关闭设备

完成工作后,您应该尽快关闭设备,这(如您所知)通过 MCI_CLOSE 命令完成。

简而言之

下表总结了您通常用于音频文件的命令及其参数结构和标志。

命令 输入/输出 参数结构 常用标志
MCI_OPEN 输入/输出 MCI_OPEN_PARMS MCI_WAIT、MCI_OPEN_ELEMENT 和 MCI_OPEN_TYPE
MCI_RECORD In MCI_RECORD_PARMS (无) 或 MCI_NOTIFY
MCI_PLAY 输出 MCI_PLAY_PARMS MCI_NOTIFY
MCI_PAUSE 输入/输出 MCI_GENERIC_PARMS MCI_WAIT
MCI_RESUME 输入/输出 MCI_GENERIC_PARMS MCI_WAIT
MCI_STOP 输入/输出 MCI_GENERIC_PARMS MCI_WAIT
MCI_SEEK 输出 MCI_SEEK_PARMS MCI_WAIT 和 MCI_TO / MCI_SEEK_TO_START / MCI_SEEK_TO_END
MCI_SAVE In MCI_SAVE_PARMS MCI_WAIT 和 MCI_SAVE_FILE
MCI_STATUS 输入/输出 MCI_STATUS_PARMS MCI_WAIT 和 MCI_STATUS_ITEM
MCI_CLOSE 输入/输出 MCI_GENERIC_PARMS MCI_WAIT

MCI 和 .NET

创建托管签名

由于 .NET 不支持 MCI 并且不允许您直接调用非托管代码,因此您需要创建自己的封送类型和 PInvoke 方法。

请记住,您可以使用 Control.Handle 属性获取窗口的句柄。

以下类是我们非托管结构和函数的托管签名以及必需的常量

internal static class SafeNativeMethods
{
    // Constants

    public const string WaveAudio = "waveaudio";

    public const uint MM_MCINOTIFY = 0x3B9;

    public const uint MCI_NOTIFY_SUCCESSFUL = 0x0001;
    public const uint MCI_NOTIFY_SUPERSEDED = 0x0002;
    public const uint MCI_NOTIFY_ABORTED = 0x0004;
    public const uint MCI_NOTIFY_FAILURE = 0x0008;

    public const uint MCI_OPEN = 0x0803;
    public const uint MCI_CLOSE = 0x0804;
    public const uint MCI_PLAY = 0x0806;
    public const uint MCI_SEEK = 0x0807;
    public const uint MCI_STOP = 0x0808;
    public const uint MCI_PAUSE = 0x0809;
    public const uint MCI_RECORD = 0x080F;
    public const uint MCI_RESUME = 0x0855;
    public const uint MCI_SAVE = 0x0813;
    public const uint MCI_LOAD = 0x0850;
    public const uint MCI_STATUS = 0x0814;

    public const uint MCI_SAVE_FILE = 0x00000100;
    public const uint MCI_OPEN_ELEMENT = 0x00000200;
    public const uint MCI_OPEN_TYPE = 0x00002000;
    public const uint MCI_LOAD_FILE = 0x00000100;
    public const uint MCI_STATUS_POSITION = 0x00000002;
    public const uint MCI_STATUS_LENGTH = 0x00000001;
    public const uint MCI_STATUS_ITEM = 0x00000100;

    public const uint MCI_NOTIFY = 0x00000001;
    public const uint MCI_WAIT = 0x00000002;
    public const uint MCI_FROM = 0x00000004;
    public const uint MCI_TO = 0x00000008;

    // Structures

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_OPEN_PARMS
    {
        public IntPtr dwCallback;
        public uint wDeviceID;
        public IntPtr lpstrDeviceType;
        public IntPtr lpstrElementName;
        public IntPtr lpstrAlias;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_RECORD_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_PLAY_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_GENERIC_PARMS
    {
        public IntPtr dwCallback;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SEEK_PARMS
    {
        public IntPtr dwCallback;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SAVE_PARMS
    {
        public IntPtr dwCallback;
        public IntPtr lpfilename;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_STATUS_PARMS
    {
        public IntPtr dwCallback;
        public uint dwReturn;
        public uint dwItem;
        public uint dwTrack;
    } ;

    // Functions

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.U4)]
    public static extern uint mciSendCommand(
        uint mciId,
        uint uMsg,
        uint dwParam1,
        IntPtr dwParam2);

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool mciGetErrorString(
        uint mcierr,
        [MarshalAs(UnmanagedType.LPStr)]
        System.Text.StringBuilder pszText,
        uint cchText);
}

接收 MCI_MMNOTIFY

在 C 语言中,您可以像处理任何其他消息一样处理 MCI_MMNOTIFY 消息。只需将处理程序添加到窗口过程 (WndProc) 函数中。

在 .NET 中,您没有 WndProc 函数。但是,.NET 使用受保护的 Control.WndProc() 函数来模拟此函数。您可以在窗体中重写此函数并执行所需处理。以下示例对此进行了说明

public partial class MainForm : Form
{
    . . . 

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == SafeNativeMethods.MM_MCINOTIFY)
        {
            // Handle the message
        }

        // DO NOT REMOVE the following line
        base.WndProc(ref m);
    }
}

示例代码

本文附带了示例应用程序 SampleRec (C#) 和 SMPREC (C)。

© . All rights reserved.