淦!
琢磨了一晚上啊, 总算有些眉目了. (所以我还是一个笨蛋啊, 明明这么简单的东西却花费了这么长时间
首先, MCI的全称是Multimedia Control Interface, 即多媒体控制接口, 通过它, 我们可以做到播放音频视频, 甚至录制音频, 虽然古老, 但是真的强大.
注意, 文章较官方文档有不少删减, 如果看标准内容, 还是看官方文档比较好.
MCI Command Strings 官方文档: Microsoft Command Strings - Win32 app | Microsoft Docs 哦对了, 文档是纯英文哦~
理论基础:
- MCI 不能与 C# 中的内存流打交道, 他只能播放文件.
- MCI 支持很多格式, 包含: CD音频, 数字音频, MIDI音乐, 视频唱片(videodisc), VCR, 以及波形音频.
- MCI 中, 被播放的音频文件被称作 设备(Device)
- MCI 中, 支持巨多设置项, 包括播放进度, 音量大小, 声道开关, 如果你播放的是 MIDI, 支持的设置更多.
正式开始:
下面, 我们将以播放音频为例, 尽可能多的讲述 MCI 的相关知识, 文章在今后可能会继续更新.
示例文件在: C:\Users\Null\Desktop\Tutorial.wav
> 引用库:
- 最最开始, 无非是载入库.
- 对于 C++, 需要引用 Windows.h 以及 Mmsystem.h 这两个头文件
- 对于 C#, using System.Runtime.InteropServices; 以进行非托管库的调用.
> 执行指令:
- MCI 中, 有 3 种方式来执行 MCI 指令. 分别是: mciSendString, mciExecute, mciSendCommand, 它们均位于 winmm.dll 中.
函数原型:
MCIERROR mciSendString( // MCIERROR 是 无符号长整形数字 LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback ); BOOL mciExecute( LPCSTR pszCommand ); MCIERROR mciSendCommand( MCIDEVICEID IDDevice, // 设备 ID, 通过另一个函数打开文件可以获得 UINT uMsg, DWORD_PTR fdwCommand, DWORD_PTR dwParam );
C# 声明方式:
[DllImport("winmm.dll", EntryPoint = "mciSendString", CharSet = CharSet.Unicode)] extern static ulong MciSendString(string command, string buffer, int bufferSize, IntPtr callback); [DllImport("winmm.dll", EntryPoint = "mciExecute", CharSet = CharSet.Unicode)] extern static bool MciExecute(string command); [DllImport("winmm.dll", EntryPoint = "mciSendCommand", CharSet = CharSet.Unicode)] extern static ulong MciSendCommand(uint deviceId, uint uMsg, IntPtr fdwCommand, IntPtr dwParam);
简略说明:
mciSendString: 最常用的方法, 通过字符串来表示一个指令, 会返回错误码, 不会有弹窗警告 mciExecute: 调试时较常用的方法, 在执行时若有未完善的地方, 会弹窗警告, 也因为如此, 程序的发布版本不会使用这个(影响使用体验) mciSendCommand: 很少用, 通过指令的ID来表示指令, 会返回错误码, 不会有弹窗警告 某些 MCI 指令具有返回值, 例如获取播放状态, 这些指令不能通过mciExecute执行.
- 文章只会通过 mciSendString 来介绍 MCI 哟
> 打开文件:
- 对于播放音频, 首要的第一件事, 肯定就是打开文件并将其载入到内存了. 不过有一点很重要, 就是 MCI 指令只支持短路径(ShortPath), 所以在拿到文件路径后, 我们得将其转换为短路径.
- 如果不对路径进行转换, 那么某些名字长度大于8的文件(准确来说是路径中任何一部分长度大于8)的将无法播放
关于短路径与长路径: windows系统下的文件长名和文件短名 短路径与长路径的转换: [C#/C/C++] GetShortPathName 详解, 长路径转换为短路径
- MCI 指令中, 通过 open 来打开一个文件, 并且在末尾还可以使用 “alias 别名” 来为这个已打开的文件起一个别名.
- 下面是两个示例:
在第一的 mciSendString 中, 很清晰明了, 打开了一个文件, 而第二个中, 我们还加了一个alias, 即, 别名, MCI支持为打开的文件起一个别名, 并且推荐这么做. 第二个种, 我们为它起的别名是 tutorial.// 文件是 C:\Users\Null\Desktop\Tutorial.wav // 转换为短路径是 C:\Users\Null\Desktop\Tut~1.wav mciSendString(@"open C:\Users\Null\Desktop\Tut~1.wav", null, 0, IntPtr.Zero); mciSendString(@"open C:\Users\Null\Desktop\Tut~1.wav alias tutorial", null, 0, IntPtr.Zero);
> 播放音频:
- 播放音频的 MCI 指令是 play, 直接 “play 设备”
- 示例:
如果你没有为文件指定别名, 那么在使用 play 指令时, 只能指定短路径 如果你为文件指定了别名, 直接play加上别名即可播放这个文件.mciSendString(@"play C:\Users\Null\Desktop\Tut~1.wav", null, 0, IntPtr.Zero); mciSendString(@"play tutorial", null, 0, IntPtr.Zero);
> 重复播放音频:
- 这里, 用到了参数, 就像alias一样, 可选. 重复播放还是使用的play指令.
- 示例:
设备名如旧, 可以直接指定短路径, 也可以指定别名, 而想做到重复播放, 只需要在最后指定repeatmciSendString(@"play C:\Users\Null\Desktop\Tut~1.wav repeat", null, 0, IntPtr.Zero); mciSendString(@"play tutorial repeat", null, 0, IntPtr.Zero);
> 同步播放音频:
- 同样是参数, 这里是wait, 支持wait参数的指令可以同步执行, 例如play指令
- 示例:
执行后, 将会阻塞当前现成, 直至播放结束.mciSendString(@"play C:\Users\Null\Desktop\Tut~1.wav wait", null, 0, IntPtr.Zero); mciSendString(@"play tutorial wait", null, 0, IntPtr.Zero);
> 暂停播放:
- 暂停, pause, 用法很简单, 同样是 “pause 设备”
- 示例:
执行后, 正在播放的音频就会暂停.mciSendString(@"pause C:\Users\Null\Desktop\Tut~1.wav", null, 0, IntPtr.Zero); mciSendString(@"pause tutorial", null, 0, IntPtr.Zero);
> 恢复播放:
- 恢复, resume, 语法是 “resume 设备”
- 示例:
执行后, 暂停播放的音频就会恢复.mciSendString(@"pause C:\Users\Null\Desktop\Tut~1.wav", null, 0, IntPtr.Zero); mciSendString(@"pause tutorial", null, 0, IntPtr.Zero);
> 关闭文件:
- 关闭, close, 语法是: “close 设备”
- 示例:
执行后, 文件会被关闭. 如果音频正在播放, 则会停止.mciSendString(@"close C:\Users\Null\Desktop\Tut~1.wav", null, 0, IntPtr.Zero); mciSendString(@"close tutorial", null, 0, IntPtr.Zero);
> 改变播放位置:
- seek 指令, 这个指令比较复杂哦. 语法如下:
seek device to position seek device to start seek device to end
- 示例:
短路径肯定是能用的哦, 我只是懒得写了, 至于 “seek device to position” 的用法, 其中position默认是ms为单位的整数时间哦, 也就是说, 1000代表1s.mciSendString(@"seek tutorial to 1000", null, 0, IntPtr.Zero); mciSendString(@"seek tutorial to start", null, 0, IntPtr.Zero); mciSendString(@"seek tutorial to end", null, 0, IntPtr.Zero);
- seek 的单位是可以调整的, 继续看哦
> 设置相关:
设置, set, 这个支持的就更多了
以下是适用于 CD Audio 的语法 : set device time format milliseconds set device time format msf set device time format tmsf
以下是适用于 Wave Audio 的语法 : set device any input set device any output set device channels channel_count set device format tag pcm set device format tag tag set device input integer set device output integer set device time format bytes set device time format milliseconds
选项 描述 time format milliseconds 将时间格式设置为毫秒, 所有使用position值的指令都将采用毫秒作为单位, 你可以将milliseconds缩写为ms. 对于音序器设备, time format msf 设置时间格式到 分钟, 妙, 以及帧. 所有使用position值的指令都见采用MSF格式(CD音频的默认格式), 请将MSF值指定为 mm:ss 的格式, mm是分钟, ss是秒, ff是帧. 如果一个字段以及后面的字段都是0, 你可以忽略掉它. 例如, 3, 3:0, 3:0:0 都是表示3分钟的正确方式. MSF字段有以下最大值, 分钟:99, 秒:59, 帧: 74. time format tmsf 将时间格式设置为 音轨, 分钟, 秒, 以及帧. 所有使用position值的指令都见采用TMSF格式, 额, 与上面的一样, 只不过多了个音轨. 音轨的最大值是99, 分钟, 秒, 帧的最大值与MSF格式一致. any input 当录制时, 使用所有支持当前格式的输入, 这是默认设置 any output 当播放时, 使用所有支持当前格式的输出, 这是默认的 time format bytes 在 PCM 文件格式中, 设置时间格式(单位)为字节, 指定这个指令后, 所有position信息都将被指定为字节格式
> 状态信息:
状态, status, 语法: “status device option”, 返回到 mciSendString 参数指定的字符串缓冲区
适用于音频的常用语法
status device position
status device length
status device mode
status device time format
选项 描述 position 获取目前播放的位置单位与时间格式统一 length 获取当前播放音频的长度 单位与时间格式统一 mode 获取播放状态, 返回的值是以下值之一: stopped / playing / paused time format 获取当前的时间格式 string buffer = new string('\0', 256); // 分配一个长度的字符串用来存放返回值 mciSendString("status tutorial position", buffer, 256, IntPtr.Zero); // 调用 Console.WriteLine(buffer.TrimEnd('\0')); // 打印返回值, TrimEnd的原因字符串的是长度是256, 函数没有使用的部分仍然是 \0 字符.
> 音频设置
设置音频, setaudio, 语法 “setaudio device option”
常用语法:
setaudio device left volume to factor
setaudio device right volume to factor
setaudio device volume to factor
选项 描述 left/right volume to factor 将指定声道的音量设置为指定值 volume to factor 将音量设置为指定值
错误码:
下面是sendMciString会返回的错误码以及描述(对名称翻译, 然后稍加校正), 哦对了, 返回 0 代表无错误哦
错误码 名称 描述 257 MCIERR_INVALID_DEVICE_ID 无效设备 ID 259 MCIERR_UNRECOGNIZED_KEYWORD 未识别关键字 261 MCIERR_UNRECOGNIZED_COMMAND 未识别的命令 262 MCIERR_HARDWARE 硬件 263 MCIERR_INVALID_DEVICE_NAME 无效的设备名称 264 MCIERR_OUT_OF_MEMORY 内存不足 265 MCIERR_DEVICE_OPEN 设备打开 266 MCIERR_CANNOT_LOAD_DRIVER 无法加载驱动程序 267 MCIERR_MISSING_COMMAND_STRING 缺少命令字符串 268 MCIERR_PARAM_OVERFLOW 参数溢出 269 MCIERR_MISSING_STRING_ARGUMENT 缺少字符串参数 270 MCIERR_BAD_INTEGER 坏整数 271 MCIERR_PARSER_INTERNAL 分析器内部 (估计是这个API内部对指令分析时出现的错误) 272 MCIERR_DRIVER_INTERNAL 驱动程序内部 273 MCIERR_MISSING_PARAMETER 缺少参数 274 MCIERR_UNSUPPORTED_FUNCTION 不支持的功能 275 MCIERR_FILE_NOT_FOUND 未找到文件 276 MCIERR_DEVICE_NOT_READY 设备未就绪 277 MCIERR_INTERNAL 内部 278 MCIERR_DRIVER 驱动器 279 MCIERR_CANNOT_USE_ALL 不能全部使用 280 MCIERR_MULTIPLE 多个 281 MCIERR_EXTENSION_NOT_FOUND 未找到扩展 282 MCIERR_OUTOFRANGE 超出范围 284 MCIERR_FLAGS_NOT_COMPATIBLE 标志不兼容 286 MCIERR_FILE_NOT_SAVED 文件未保存 287 MCIERR_DEVICE_TYPE_REQUIRED 需要设备类型 288 MCIERR_DEVICE_LOCKED 设备已锁定 289 MCIERR_DUPLICATE_ALIAS 重复别名 290 MCIERR_BAD_CONSTANT 坏常量 291 MCIERR_MUST_USE_SHAREABLE 必须使用可共享 292 MCIERR_MISSING_DEVICE_NAME 缺少设备名称 293 MCIERR_BAD_TIME_FORMAT 错误的时间格式 294 MCIERR_NO_CLOSING_QUOTE 没有关闭中的引用 295 MCIERR_DUPLICATE_FLAGS 重复标志 296 MCIERR_INVALID_FILE 无效文件 297 MCIERR_NULL_PARAMETER_BLOCK 空参数块 298 MCIERR_UNNAMED_RESOURCE 未命名的资源 299 MCIERR_NEW_REQUIRES_ALIAS 新需要别名 300 MCIERR_NOTIFY_ON_AUTO_OPEN 自动打开时通知 301 MCIERR_NO_ELEMENT_ALLOWED 不允许任何元素 302 MCIERR_NONAPPLICABLE_FUNCTION 不可应用功能 303 MCIERR_ILLEGAL_FOR_AUTO_OPEN 非法自动打开 304 MCIERR_FILENAME_REQUIRED 需要文件名 305 MCIERR_EXTRA_CHARACTERS 额外字符 (可能是指多出了一些不需要的字符) 306 MCIERR_DEVICE_NOT_INSTALLED 未安装设备 307 MCIERR_GET_CD 获取 CD 308 MCIERR_SET_CD 设置 CD 309 MCIERR_SET_DRIVE 设置驱动器 310 MCIERR_DEVICE_LENGTH 设备长度 311 MCIERR_DEVICE_ORD_LENGTH 设备 ORD 长度 312 MCIERR_NO_INTEGER 无整数 320 MCIERR_WAVE_OUTPUTSINUSE 波输出 321 MCIERR_WAVE_SETOUTPUTINUSE 波设置输出 322 MCIERR_WAVE_INPUTSINUSE 波输入使用 323 MCIERR_WAVE_SETINPUTINUSE 波设置 324 MCIERR_WAVE_OUTPUTUNSPECIFIED 波输出未指定 325 MCIERR_WAVE_INPUTUNSPECIFIED 波输入未指定 326 MCIERR_WAVE_OUTPUTSUNSUITABLE 波输出可居住 327 MCIERR_WAVE_SETOUTPUTUNSUITABLE 波设置通普通西装 328 MCIERR_WAVE_INPUTSUNSUITABLE 波输入可居住 329 MCIERR_WAVE_SETINPUTUNSUITABLE 波设置通图适合 336 MCIERR_SEQ_DIV_INCOMPATIBLE Seq Div 不兼容 337 MCIERR_SEQ_PORT_INUSE SEQ 端口 INUSE 338 MCIERR_SEQ_PORT_NONEXISTENT Seq 端口不存在 339 MCIERR_SEQ_PORT_MAPNODEVICE Seq 端口地图无设备 340 MCIERR_SEQ_PORT_MISCERROR SEQ 杂项错误 341 MCIERR_SEQ_TIMER SEQ 定时器 342 MCIERR_SEQ_PORTUNSPECIFIED SEQ 端口未指定 343 MCIERR_SEQ_NOMIDIPRESENT SEQ 没有MIDI在场 346 MCIERR_NO_WINDOW 无窗口 347 MCIERR_CREATEWINDOW 创建窗口 348 MCIERR_FILE_READ 文件读取 349 MCIERR_FILE_WRITE 文件写入 350 MCIERR_NO_IDENTITY 无标识
封装类:
去这里吧, 在我的另一篇文章中 [C#] 音乐播放 3 种方式 Demo 与 MCI 音乐播放器封装类. o(〃’▽’〃)o
附加内容:
- 下面是我的一些发现
- WinForm 程序使用 MCI 是可以打开 MP3 文件的, 但是如果是控制台程序, 就会出现错误, 错误码266, “MCIERR_CANNOT_LOAD_DRIVER”
- MCI 的某些指令不能正常使用, 但其实并不是很影响, 例如, “set device audio left/right off/off”, 无法正常使用.
- 音量控制我目前还是没弄成… 不过可以确认的是, 进度获取, 调整, 长度获取是没问题的, 有这些最基本的, 就差不多公用了呢
放一张我写文章时的照片吧 , 原文贴的哪都是 (笑