《Windows API每日一练》23.1 Windows多媒体

本节我们将讲述单线程到多线程的演进过程,以及进程与线程的区别。

本节必须掌握的知识点:

        多媒体硬件

        API 概述

        第172练:交互输入MCI命令

23.1.1多媒体硬件

多媒体硬件是指用于处理音频、视频和其他多媒体内容的硬件设备。在计算机系统中,常见的多媒体硬件包括:

●声卡(音频设备):声卡是用于处理和播放音频的硬件设备。它包括音频输入和输出接口,用于连接麦克风、扬声器、耳机等外部音频设备。声卡通常包含音频处理器和数字模拟转换器(DAC),用于将数字音频信号转换为模拟信号以供扬声器播放。

●显卡(视频设备):显卡是用于处理和显示图像和视频的硬件设备。它包括图形处理器(GPU)和视频输出接口(如HDMI、VGA、DisplayPort等),用于将图像和视频信号发送到显示器。显卡能够加速图形渲染和视频解码,提供流畅的图形和视频体验。

●摄像头:摄像头是用于捕捉视频和图像的设备。它通常包含图像传感器、图像处理器和接口,用于捕捉、处理和传输视频数据。摄像头广泛应用于视频通话、视频会议、监控系统等场景。

●麦克风和扬声器:麦克风用于捕捉声音和语音,而扬声器用于播放声音。这些设备通常与声卡或音频接口连接,用于音频输入和输出。

除了上述硬件设备,还有其他多媒体相关的设备,如光驱(用于读取光盘中的音频和视频内容)、投影仪(用于投影图像和视频)、电视卡(用于接收和播放电视信号)等。

多媒体硬件与操作系统之间通过驱动程序进行交互。操作系统提供相应的驱动程序接口,用于控制和管理多媒体硬件设备,以及提供对硬件功能的访问和控制。

开发人员可以使用操作系统提供的多媒体API和库,或者使用专门的多媒体开发工具包(如DirectX、Media Foundation等)与多媒体硬件进行交互,实现音频、视频的采集、处理和播放等功能。

最常用的多媒体硬件大概就是波形音频设备了,通常被称为声卡。波形音频设备将麦克风输入或其他模拟音频输入转换为数字化的样本,然后把它们存储在内存中或储存为 以.WAV扩展名结尾的硬盘文件。波形音频设备也可以将波形转换回模拟声音,再通过PC 的扬声器来播放。

声卡通常还包含一个MIDI设备。MIDI是行业标准的乐器数字接口(Musical Instrument Digital Interface)。这种硬件能根据简短的二进制信息来演奏音符。MIDI硬件通常还可以通 过电缆连接到一个MIDI输入设备,如音乐键盘。此外,外部MIDI合成器往往也可以和声卡相连。

大多数现在的计算机所连接的CD-ROM驱动器,通常都能够直接播放普通的音乐CD。 这就是所谓的“CD音频“。波形音频设备、MIDI设备和CD音频设备的输出,往往混合在一起,用户通过Windows的”音量控制“设定可以控制它们的音量。

另些常见的多媒体“设备”不需要任何额外的硬件。Windows的标准视频设备(也称为AVI视频设备)可以播放以“.AVI”(audio-video interleave,音视频交叠)为扩展名的电影或动画文件。ActiveMovie控件可以播放包括QuickTime和MPEG在内的一些其他类型的电影。PC的显卡上还可能带有专门的硬件来协助播放这些电影。

23.1.2 API 概述

Windows中对多媒体功能的API支持主要分为两个集合,分别被称为“底层”接口和“高层”接口。

多媒体定时器函数

在Windows操作系统中,有一组多媒体定时器函数可用于实现高精度的定时器操作。这些函数能够提供毫秒级别的定时器精度,并且适用于多媒体应用程序和实时系统。以下是一些常用的Windows多媒体定时器函数:

●timeSetEvent: 创建一个定时器事件,用于定期触发回调函数。

UINT_PTR timeSetEvent(

  UINT          uDelay,

  UINT          uResolution,

  LPTIMECALLBACK lpTimeProc,

  DWORD_PTR     dwUser,

  UINT          fuEvent

);

uDelay: 定时器事件的延迟时间,以毫秒为单位。

uResolution: 定时器事件的最小分辨率,以毫秒为单位。

lpTimeProc: 回调函数指针,用于处理定时器事件。

dwUser: 用户自定义数据,将传递给回调函数。

fuEvent: 定时器事件的类型和选项。

●timeKillEvent: 终止先前创建的定时器事件。

MMRESULT timeKillEvent(

  UINT_PTR uTimerID

);

uTimerID: 先前创建的定时器事件的标识符。

这些多媒体定时器函数提供了一种高精度的定时器机制,可以用于实现音频、视频的同步、动画效果、实时数据处理等场景。通过创建定时器事件并指定回调函数,可以在指定的延迟时间后触发回调函数的执行。定时器事件可以定期重复执行,或者只触发一次。

【注意】这些函数是以毫秒为单位的时间间隔来操作定时器,但它们的实际精度取决于系统的能力和负载。在使用这些函数时,建议根据实际需求和系统性能进行适当的调整和测试,以确保定时器事件的精确性和可靠性。

此外,还有其他一些定时器相关的函数和结构体,如timeBeginPeriod、timeEndPeriod、TIMECAPS等,用于设置定时器的最小分辨率、获取系统定时器能力等。这些函数和结构体可以帮助开发人员更好地管理和调整定时器的行为。

“底层”接口函数

●waveInGetNumDevs:获取系统中音频输入设备的数量。

UINT waveInGetNumDevs(void);

该函数不接受任何参数。调用该函数将返回一个无符号整数(UINT),表示当前系统中可用的音频输入设备数量。

以下是使用waveInGetNumDevs函数获取音频输入设备数量的示例代码:

#include <stdio.h>

#include <mmsystem.h>

int main() {

    UINT numDevices = waveInGetNumDevs();

    printf("Number of audio input devices: %u\n", numDevices);

    return 0;

}

上述示例代码中,通过调用waveInGetNumDevs函数获取音频输入设备数量,并将结果打印到控制台。

【注意】该函数属于Windows Multimedia API,因此在编译时需要链接winmm.lib库。

●waveInGetDevCaps:获取指定音频输入设备的能力和属性。

MMRESULT waveInGetDevCaps(

  UINT_PTR       uDeviceID,

  LPWAVEINCAPS   pwic,

  UINT           cbwic

);

该函数接受三个参数:

uDeviceID: 音频输入设备的标识符,可以是从0开始的设备索引值。

pwic: 指向WAVEINCAPS结构的指针,用于接收音频输入设备的能力和属性信息。

cbwic: WAVEINCAPS结构的大小,以字节为单位。

WAVEINCAPS结构定义如下:

typedef struct wavein_caps_tag {

  WORD  wMid;                       // 厂商ID

  WORD  wPid;                        // 产品ID

  MMVERSION vDriverVersion;             // 驱动程序版本

  TCHAR szPname[MAXPNAMELEN];      // 设备名称

  DWORD dwFormats;                   // 支持的数据格式

  WORD  wChannels;                   // 支持的通道数

  WORD  wReserved1;                  // 保留字段

} WAVEINCAPS, *PWAVEINCAPS, *NPWAVEINCAPS, *LPWAVEINCAPS;

以下是使用waveInGetDevCaps函数获取音频输入设备能力和属性的示例代码:

#include <stdio.h>

#include <mmsystem.h>

int main() {

    UINT numDevices = waveInGetNumDevs();

    if (numDevices == 0) {

        printf("No audio input devices found.\n");

        return 0;

    }

    for (UINT i = 0; i < numDevices; i++) {

        WAVEINCAPS caps;

        MMRESULT result = waveInGetDevCaps(i, &caps, sizeof(caps));

        if (result == MMSYSERR_NOERROR) {

            printf("Device %u:\n", i);

            printf("  Manufacturer ID: %u\n", caps.wMid);

            printf("  Product ID: %u\n", caps.wPid);

            printf("  Driver Version: %u\n", caps.vDriverVersion);

            printf("  Device Name: %s\n", caps.szPname);

            printf("  Supported Formats: 0x%08X\n", caps.dwFormats);

            printf("  Supported Channels: %u\n", caps.wChannels);

            printf("\n");

        }

    }

    return 0;

}

上述示例代码中,首先使用waveInGetNumDevs函数获取系统中音频输入设备的数量。然后,使用循环遍历每个设备,并调用waveInGetDevCaps函数获取设备的能力和属性信息,将其打印到控制台。

●waveInOpen:打开一个音频输入设备。

MMRESULT waveInOpen(

  LPHWAVEIN       phwi,

  UINT            uDeviceID,

  LPCWAVEFORMATEX pwfx,

  DWORD_PTR       dwCallback,

  DWORD_PTR       dwCallbackInstance,

  DWORD           fdwOpen

);

该函数接受六个参数:

phwi: 指向HWAVEIN句柄的指针,用于接收打开的音频输入设备句柄。

uDeviceID: 音频输入设备的标识符,可以是从0开始的设备索引值。

pwfx: 指向WAVEFORMATEX结构的指针,定义音频流的格式。

dwCallback: 回调函数的指针,用于接收音频输入数据的回调。

dwCallbackInstance: 回调函数的实例句柄,用于标识回调函数的实例。

fdwOpen: 打开标志,用于指定打开音频输入设备的方式和特性。

WAVEFORMATEX结构定义了音频流的格式,包括采样率、位深度、通道数等信息。该结构的定义如下:

typedef struct tWAVEFORMATEX {

  WORD  wFormatTag;        // 音频数据格式

  WORD  nChannels;          // 通道数

  DWORD nSamplesPerSec;     // 采样率

  DWORD nAvgBytesPerSec;     // 平均字节率

  WORD  nBlockAlign;          // 数据块对齐方式

  WORD  wBitsPerSample;      // 位深度

  WORD  cbSize;              // 额外信息的大小

} WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX;

以下是使用waveInOpen函数打开音频输入设备的示例代码:

#include <stdio.h>

#include <mmsystem.h>

void CALLBACK WaveInCallback(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,

DWORD_PTR dwParam1, DWORD_PTR dwParam2) {

    // 处理音频输入数据的回调函数

}

int main() {

    UINT numDevices = waveInGetNumDevs();

    if (numDevices == 0) {

        printf("No audio input devices found.\n");

        return 0;

    }

    // 配置音频流的格式

    WAVEFORMATEX format;

    format.wFormatTag = WAVE_FORMAT_PCM;

    format.nChannels = 2;

    format.nSamplesPerSec = 44100;

    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nChannels * 2;

    format.nBlockAlign = format.nChannels * 2;

    format.wBitsPerSample = 16;

    format.cbSize = 0;

    HWAVEIN hwi;

    MMRESULT result = waveInOpen(&hwi, WAVE_MAPPER, &format,

(DWORD_PTR)WaveInCallback, 0, CALLBACK_FUNCTION);

    if (result != MMSYSERR_NOERROR) {

        printf("Failed to open audio input device.\n");

        return 0;

    }

    // 音频输入设备已成功打开,可以进行音频流捕获

    // ...

    // 关闭音频输入设备

    waveInClose(hwi);

    return 0;

}

上述示例代码中,首先使用waveInGetNumDevs函数获取系统中音频输入设备的数量。然后,配置音频流的格式,创建WAVEFORMATEX结构。接下来,调用waveInOpen函数打开音频输入设备,指定音频流的格式、回调函数等参数。如果打开成功,将获得音频输入设备的句柄(HWAVEIN)。在后续的音频流捕获过程中,可以使用该句柄进行操作。

●waveInStart:开始音频输入流的捕获。

MMRESULT waveInStart(

  HWAVEIN hwi

);

该函数接受一个参数:

hwi:要开始数据捕获的音频输入设备的句柄(HWAVEIN)。

以下是使用waveInStart函数开始音频输入设备数据捕获的示例代码:   

// 开始数据捕获

result = waveInStart(hwi);

if (result != MMSYSERR_NOERROR) {

   printf("Failed to start audio input device.\n");

   waveInClose(hwi);

   return 0;

}

●waveInStop:停止音频输入流的捕获。

MMRESULT waveInStop(

  HWAVEIN hwi

);

该函数接受一个参数:

hwi:要停止数据捕获的音频输入设备的句柄(HWAVEIN)。

以下是使用waveInStop函数停止音频输入设备数据捕获的示例代码:

// 停止数据捕获

result = waveInStop(hwi);

if (result != MMSYSERR_NOERROR) {

    printf("Failed to stop audio input device.\n");

    waveInClose(hwi);

    return 0;

}

●waveInClose:关闭先前打开的音频输入设备。

MMRESULT waveInClose(

  HWAVEIN hwi

);

该函数接受一个参数:

hwi:要关闭的音频输入设备的句柄(HWAVEIN)。

以下是使用waveInClose函数关闭音频输入设备的示例代码:

// 关闭音频输入设备

result = waveInClose(hwi);

if (result != MMSYSERR_NOERROR) {

    printf("Failed to close audio input device.\n");

    return 0;

}

●waveOutGetNumDevs:获取系统中音频输出设备的数量。

UINT waveOutGetNumDevs();

该函数没有参数。它返回一个无符号整数(UINT),表示系统中可用的音频输出设备数量。

以下是使用waveOutGetNumDevs函数获取音频输出设备数量的示例代码:

#include <stdio.h>

#include <mmsystem.h>

int main() {

    UINT numDevices = waveOutGetNumDevs();

    if (numDevices == 0) {

        printf("No audio output devices found.\n");

    } else {

        printf("Number of audio output devices: %u\n", numDevices);

    }

   

    return 0;

}

以上示例代码中,调用waveOutGetNumDevs函数获取音频输出设备的数量。如果返回的结果为0,表示系统中没有可用的音频输出设备。否则,打印出音频输出设备的数量。

●waveOutGetDevCaps:获取指定音频输出设备的能力和属性。

MMRESULT waveOutGetDevCaps(

  UINT           uDeviceID,

  LPWAVEOUTCAPS  pwoc,

  UINT           cbwoc

);

该函数接受三个参数:

uDeviceID:要获取能力信息的音频输出设备的ID。可以使用0到waveOutGetNumDevs函数返回的设备数量减1之间的值,来表示不同的设备。

pwoc:指向WAVEOUTCAPS结构体的指针,用于接收设备的能力信息。

cbwoc:指定pwoc缓冲区的大小,以字节为单位。

以下是使用waveOutGetDevCaps函数获取音频输出设备能力信息的示例代码:

#include <stdio.h>

#include <mmsystem.h>

int main() {

    UINT numDevices = waveOutGetNumDevs();

    if (numDevices == 0) {

        printf("No audio output devices found.\n");

        return 0;

    }

    for (UINT i = 0; i < numDevices; i++) {

        WAVEOUTCAPS woc;

        MMRESULT result = waveOutGetDevCaps(i, &woc, sizeof(WAVEOUTCAPS));

        if (result == MMSYSERR_NOERROR) {

            printf("Device ID: %u\n", i);

            printf("Device Name: %s\n", woc.szPname);

            printf("Device Channels: %u\n", woc.wChannels);

            printf("Device Formats: 0x%04X\n", woc.dwFormats);

            // 可以输出其他设备能力信息

            printf("\n");

        } else {

            printf("Failed to get device capabilities for ID %u\n", i);

        }

    }

    return 0;

}

在上述示例代码中,首先通过waveOutGetNumDevs函数获取音频输出设备的数量。然后,使用循环遍历每个设备的ID,并调用waveOutGetDevCaps函数获取每个设备的能力信息。通过WAVEOUTCAPS结构体,可以获取设备的名称、通道数、支持的格式等信息。

【注意】该函数属于Windows Multimedia API,因此在编译时需要链接win

●waveOutOpen:打开一个音频输出设备。

MMRESULT waveOutOpen(

  LPHWAVEOUT      phwo,

  UINT            uDeviceID,

  LPWAVEFORMATEX  pwfx,

  DWORD_PTR       dwCallback,

  DWORD_PTR       dwCallbackInstance,

  DWORD           fdwOpen

);

该函数接受六个参数:

phwo:指向HWAVEOUT类型的指针,用于接收打开的音频输出设备的句柄。

uDeviceID:要打开的音频输出设备的ID。可以使用0到waveOutGetNumDevs函数返回的设备数量减1之间的值,来表示不同的设备。

pwfx:指向WAVEFORMATEX结构体的指针,用于指定音频流的格式。

dwCallback:回调函数的地址或回调函数标识符。

dwCallbackInstance:用户定义的回调函数实例标识符。

fdwOpen:打开设备的标志,可以是以下值之一或它们的组合:

CALLBACK_NULL:无回调函数。

CALLBACK_WINDOW:回调函数是一个窗口过程。

CALLBACK_FUNCTION:回调函数是一个函数地址。

以下是使用waveOutOpen函数打开音频输出设备的示例代码:

#include <stdio.h>

#include <mmsystem.h>

void CALLBACK WaveOutCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR

dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {

    // 处理音频输出数据的回调函数

}

int main() {

    UINT numDevices = waveOutGetNumDevs();

    if (numDevices == 0) {

        printf("No audio output devices found.\n");

        return 0;

    }

    // 配置音频流的格式

    WAVEFORMATEX format;

    format.wFormatTag = WAVE_FORMAT_PCM;

    format.nChannels = 2;

    format.nSamplesPerSec = 44100;

    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nChannels * 2;

    format.nBlockAlign = format.nChannels * 2;

    format.wBitsPerSample = 16;

    format.cbSize = 0;

    HWAVEOUT hwo;

    MMRESULT result = waveOutOpen(&hwo, WAVE_MAPPER, &format,

 (DWORD_PTR)WaveOutCallback, 0, CALLBACK_FUNCTION);

    if (result != MMSYSERR_NOERROR) {

        printf("Failed to open audio output device.\n");

        return 0;

    }

    // 音频输出设备已成功打开,可以进行音频流播放

    // ...

    // 关闭音频输出设备

    result = waveOutClose(hwo);

    if (result != MMSYSERR_NOERROR) {

        printf("Failed to close audio output device.\n");

        return 0;

    }

    // 音频输出设备已成功关闭

    return 0;

}

在上述示例代码中,首先通过waveOutGetNumDevs函数获取音频输出设备的数量。然后,使用waveOutOpen函数打开音频输出设备,并将音频流的格式信息以及回调函数的地址传递给函数。通过回调函数,可以处理音频输出数据,例如实现音频的实时处理或可视化。

●waveOutWrite:将音频数据写入音频输出设备进行播放。

MMRESULT waveOutWrite(

  HWAVEOUT   hwo,

  LPWAVEHDR  pwh,

  UINT       cbwh

);

该函数接受三个参数:

hwo:已打开的音频输出设备的句柄,通过调用waveOutOpen函数获取。

pwh:指向WAVEHDR结构体的指针,该结构体描述了待播放的音频数据块。

cbwh:pwh缓冲区的大小,以字节为单位。

以下是使用waveOutWrite函数将音频数据写入音频输出设备进行播放的示例代码:

// 创建音频数据块

WAVEHDR waveHdr;

waveHdr.lpData = // 音频数据的指针

waveHdr.dwBufferLength = // 音频数据的长度,以字节为单位

waveHdr.dwFlags = 0;

waveHdr.dwLoops = 0;

// 将音频数据块写入音频输出设备进行播放

result = waveOutWrite(hwo, &waveHdr, sizeof(WAVEHDR));

if (result != MMSYSERR_NOERROR) {

    printf("Failed to write audio data to output device.\n");

    return 0;

}

●waveOutReset:停止并清空音频输出设备的缓冲区。

MMRESULT waveOutReset(

  HWAVEOUT hwo

);

该函数接受一个参数:

hwo:已打开的音频输出设备的句柄,通过调用waveOutOpen函数获取。

以下是使用waveOutReset函数重置音频输出设备的示例代码:

// 等待一段时间后,重置音频输出设备

Sleep(5000); // 等待5秒

result = waveOutReset(hwo);

if (result != MMSYSERR_NOERROR) {

    printf("Failed to reset audio output device.\n");

    return 0;

}

●waveOutClose:关闭先前打开的音频输出设备。

MMRESULT waveOutClose(

  HWAVEOUT hwo

);

该函数接受一个参数:

hwo:已打开的音频输出设备的句柄,通过调用waveOutOpen函数获取。

以下是使用waveOutClose函数关闭音频输出设备的示例代码:

// 关闭音频输出设备

result = waveOutClose(hwo);

if (result != MMSYSERR_NOERROR) {

   printf("Failed to close audio output device.\n");

   return 0;

}

“高层”接口函数

MCI(Media Control Interface)是Windows操作系统提供的一组函数,用于控制和操作多媒体设备和资源,如CD-ROM驱动器、音频播放器等。以下是一些常用的MCI函数:

●mciSendCommand: 发送命令给多媒体设备。

DWORD mciSendCommand(

  MCIDEVICEID IDDevice,

  UINT        uMsg,

  DWORD_PTR   fdwCommand,

  DWORD_PTR   dwParam

);

IDDevice: 多媒体设备的标识符。

uMsg: 发送的命令消息。

fdwCommand: 命令标志和选项。

dwParam: 命令参数。

●mciSendString: 以字符串形式发送命令给多媒体设备。

DWORD mciSendString(

  LPCTSTR lpszCommand,

  LPTSTR  lpszReturnString,

  UINT    cchReturn,

  HANDLE  hwndCallback

);

lpszCommand: 命令字符串。

lpszReturnString: 存储返回结果的缓冲区。

cchReturn: 缓冲区的大小。

hwndCallback: 回调窗口的句柄。

●mciGetErrorString: 获取MCI错误消息的文本描述。

BOOL mciGetErrorString(

  DWORD   fdwError,

  LPTSTR  lpszErrorText,

  UINT    cchErrorText

);

fdwError: 错误代码。

lpszErrorText: 存储错误消息的缓冲区。

cchErrorText: 缓冲区的大小。

这些函数可以通过发送命令来控制多媒体设备的行为,例如播放音频、暂停、停止、调整音量等。你可以使用预定义的命令字符串,也可以自定义命令字符串来实现特定的功能。MCI函数提供了一种相对简单的方式来控制多媒体设备,适用于一些简单的多媒体操作场景。但对于更复杂的多媒体处理,如音频编码、视频渲染等,可能需要使用更高级别的多媒体框架和API,如Media Foundation或DirectShow,这些内容本书并不涉及,有兴趣的读者可以查阅MSDN等相关资料。

23.1.3 第172练:交互输入MCI命令

TESTMCI.C

/*------------------------------------------------------------------------

 172 WIN32 API 每日一练

     第172个例子TESTMCI.C:交互输入MCI命令

     DialogBox 函数

     DLGPROC回调函数

     SetWindowPos函数 

     EM_LINEFROMCHAR消息

     EM_GETSEL消息

     mciSendString函数

     mciGetErrorString函数

     SetDlgItemText函数

     EnableWindow函数

 (c) www.bcdaren.com 编程达人

-----------------------------------------------------------------------*/

#include <Windows.h>

#include "resource.h"

#pragma comment(lib,"WINMM.lib"//须用到WINMM.DLL的导入库

#define ID_TIMER 1

BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szAppName[] = TEXT("TestMci");

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

{

     if (-1 == DialogBox(hInstance, szAppName, NULL, DlgProc))

     {

          MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);

     }

     return 0;

}

BOOL CALLBACK DlgProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)

{

     static HWND hwndEdit;

     int iCharBeg,iCharEnd,iLineBeg,iLineEnd,iChar,iLine,iLength;

     MCIERROR error;

     RECT rect;

     TCHAR szCommand[1024],szReturn[1024],szError[1024],szBuffer[32];

    

     switch (message)

     {

     case WM_INITDIALOG:

         //将窗口置于屏幕中心,保留当前的Z顺序|保留当前大小(忽略cx和cy参数)。

          GetWindowRect(hwnd,&rect);

          SetWindowPos(hwnd,NULL,

                   (GetSystemMetrics(SM_CXSCREEN) - rect.right + rect.left) /2,

                   (GetSystemMetrics(SM_CYSCREEN) - rect.bottom + rect.top) /2,

               0, 0, SWP_NOZORDER | SWP_NOSIZE);

          hwndEdit = GetDlgItem(hwnd,IDC_MAIN_EDIT);//获取对话框编辑控件句柄

          SetFocus(hwndEdit);//手动设置焦点

          return FALSE;

     case WM_COMMAND:

          switch (LOWORD(wParam))

          {

          case IDOK:

               //计算文本框中被选中的开始和结束的行数

       //开始位置和结束位置

SendMessage(hwndEdit,EM_GETSEL,(WPARAM)&iCharBeg,(LPARAM)&iCharEnd);

 //获取开始的行号

          iLineBeg = SendMessage(hwndEdit,EM_LINEFROMCHAR,iCharBeg,0);

//获取结束的行号

          iLineEnd = SendMessage(hwndEdit,EM_LINEFROMCHAR,iCharEnd,0);

          //遍历所有选中的行,如果没有选中行,则默认读取最后一行文本

          for (iLine = iLineBeg;iLine <= iLineEnd;iLine++)

          {

/*每次循环获取第iLine行文本,并在最末尾加上\0结束,如果是空行,

则跳过。缓冲区第1个字为缓冲区字符个数(含\0),EM_GETLINE消息要求的*/

               *(WORD*)szCommand = sizeof(szCommand) / sizeof(TCHAR);

               iLength = SendMessage(hwndEdit, EM_GETLINE, iLine,

(LPARAM)szCommand);

               szCommand[iLength] = '\0';

               //跳过空行

               if(iLength == 0)

                   continue;

                //发送MCI命令到MCI设备,并返回错误信息

                error = mciSendString(szCommand,szReturn,sizeof(szReturn) /

 sizeof(TCHAR),hwnd);

                //显示返回的消息文本

                SetDlgItemText(hwnd,IDC_RETURN_STRING,szReturn);

                //显示错误信息

                mciGetErrorString(error,szError,sizeof(szError) /

sizeof(TCHAR));

                SetDlgItemText(hwnd,IDC_ERROR_STRING,szError);

               }

                //将插入符caret放在选定行中最后一行的后面,EM_LINEINDEX获取第

//iLineEnd行,第1个字符的位置索引

               iChar = SendMessage(hwndEdit, EM_LINEINDEX, iLineEnd, 0);                                                                         

//获取指定位置的字符(iCharEnd)所在行的字符个数

               iChar += SendMessage(hwndEdit, EM_LINELENGTH, iCharEnd, 0);

                                                                           //iChar指定该行最后一个字符的后面。

//选择该行最后一个字符

               SendMessage(hwndEdit, EM_SETSEL, iChar, iChar);

               //插入回车换行符

               SendMessage(hwndEdit,EM_REPLACESEL,FALSE,(LPARAM)TEXT("\r\n"));

               SetFocus(hwndEdit);

               return TRUE;

          case IDCANCEL:

               EndDialog(hwnd,0);

               return TRUE;

          case IDC_MAIN_EDIT:

               if (HIWORD(wParam) == EN_ERRSPACE)

               {

                    MessageBox(hwnd,TEXT("Error control out of space."),

                              szAppName,MB_OK | MB_ICONINFORMATION);

                    return TRUE;

               }

               break;

          }

          break;

    //通知一个应用程序,一个MCI装置已经完成的动作。

//仅当使用MCI_NOTIFY标志时,MCI设备才会发送此消息。

     case MM_MCINOTIFY:

//启用IDC_NOTIFY_MESSAGE控件

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_MESSAGE), TRUE);

          wsprintf(szBuffer, TEXT("Device ID = %i"), lParam);

          SetDlgItemText(hwnd, IDC_NOTIFY_ID, szBuffer);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ID), TRUE);

          //成功、取代、中止、失败

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUCCESSFUL), wParam &

MCI_NOTIFY_SUCCESSFUL);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUPERSEDED), wParam &

MCI_NOTIFY_SUPERSEDED);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ABORTED), wParam &

MCI_NOTIFY_ABORTED);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_FAILURE), wParam &

MCI_NOTIFY_FAILURE);

          SetTimer(hwnd, ID_TIMER, 5000, NULL);

          return TRUE;

     case WM_TIMER:

          KillTimer(hwnd,ID_TIMER);

//禁用IDC_NOTIFY_MESSAGE控件

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_MESSAGE), FALSE);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ID), FALSE);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUCCESSFUL), FALSE);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUPERSEDED), FALSE);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ABORTED), FALSE);

          EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_FAILURE), FALSE);

          return TRUE;

     case WM_SYSCOMMAND:

          switch(LOWORD(wParam))

          {

          case SC_CLOSE:

               EndDialog(hwnd,0);

               return TRUE;

          }

          break;

     }

     return FALSE;

}

/******************************************************************************

DialogBox 函数:它的作用是从一个对话框资源中创建一个模态对话框。该函数直到指定的回调函数通过调用EndDialog函数中止模态的对话框才能返回控制。

void DialogBoxA(

   hInstance,//包含对话框模板的模块的句柄。如果此参数为NULL,则使用当前可执行文件。

   lpTemplate,//对话框模板。该参数是指向以空值结尾的字符串的指针,该字符串指定对话框模板的名称,或者为整数值,该整数值指定对话框模板的资源标识符。

   hWndParent,//拥有对话框的窗口的句柄。

   lpDialogFunc//指向对话框过程的指针。

);

*******************************************************************************

DLGPROC回调函数:

由CreateDialog和DialogBox函数系列使用的应用程序定义的回调函数。它处理发送到模式对话框或无模式对话框的消息。

所述DLGPROC类型定义一个指向这个回调函数。DialogProc是应用程序定义的函数名称的占位符。

DLGPROC Dlgproc;

INT_PTR Dlgproc(

  HWND Arg1,//对话框的句柄。

  UINT Arg2,//指定消息。

  WPARAM Arg3,//指定消息特定的其他信息。

  LPARAM Arg4//指定消息特定的其他信息。

)

{...}

*******************************************************************************

SetWindowPos函数:改变一个子窗口,弹出式窗口或顶层窗口的尺寸,位置和Z序。子窗口,弹出式窗口,

及顶层窗口根据它们在屏幕上出现的顺序排序、顶层窗口设置的级别最高,并且被设置为Z序的第一个窗口。

BOOL SetWindowPos(

  HWND hWnd,//窗口的句柄

  HWND hWndInsertAfter,//在Z顺序中位于定位的窗口之前的窗口的句柄。此参数必须是窗口句柄或以下值之一MSDN。

  int  X,//窗口左侧的新位置,以客户坐标表示。

  int  Y,//窗口顶部的新位置,以客户坐标表示。

  int  cx,//窗口的新宽度,以像素为单位。

  int  cy,//窗口的新高度,以像素为单位。

  UINT uFlags//窗口大小和位置标志。

);

*******************************************************************************

EM_LINEFROMCHAR消息:获取多行编辑控件中包含指定字符索引的行的索引。字符索引是从编辑控件开始的字符从零开始的索引。

您可以将此消息发送到编辑控件或丰富的编辑控件。

参数wParam

要检索其编号的行中包含的字符的字符索引。如果此参数为-1,则EM_LINEFROMCHAR检索当前行(包含插入号的行)的行号,

或者如果存在选择,则检索包含选择的开始的行的行号。

*******************************************************************************

EM_GETSEL消息:在编辑控件中获取当前所选内容的开始和结束字符位置(以TCHAR形式)。您可以将此消息发送到编辑控件或丰富的编辑控件。

参数wParam

指向DWORD值的指针,该值接收选择的起始位置。此参数可以为NULL。

*******************************************************************************

mciSendString函数:功能发送一个命令串到MCI设备。命令字符串中指定了发送命令的设备。并返回错误

MCIERROR mciSendString(

   LPCTSTR lpszCommand,//指向以空值结尾的字符串,该字符串指定MCI命令字符串。

   LPTSTR  lpszReturnString,//指向接收返回信息的缓冲区的指针。如果不需要返回信息,则此参数可以为NULL。

   UINT    cchReturn,//指定的返回缓冲区的大小(以字符为单位)。

   HANDLE  hwndCallback//如果在命令字符串中指定了“ notify”标志,则处理到回调窗口。

);

返回值

如果成功则返回零,否则返回错误。返回的DWORD值的低位字包含错误返回值。如果错误是特定于设备的,则返回值的高位字是驱动程序标识符;

否则,高阶字为零。有关可能的错误值的列表,请参见MCIERR返回值。

*******************************************************************************

mciGetErrorString函数:检索说明指定的MCI错误代码的字符串。

BOOL mciGetErrorString(

   DWORD  fdwError,//返回的错误代码。

   LPTSTR lpszErrorText,//指向缓冲区的指针,该缓冲区接收以空值结尾的字符串,该字符串描述指定的错误。

   UINT   cchErrorText//缓冲区的长度(以字符为单位),由lpszErrorText参数指向。

);

*******************************************************************************

SetDlgItemText函数:在对话框中设置控件的标题或文本。

BOOL SetDlgItemText(

  HWND hDlg,//处理包含该控件的对话框。

  int nIDDlgItem,//标识带有要设置的标题或文本的控件。

  LPCTSTR lpString//长指针,指向以空值结尾的字符串,该字符串包含要复制到控件的文本。

);

*******************************************************************************

EnableWindow函数:该函数允许/禁止指定的窗口或控件接受鼠标和键盘的输入,当输入被禁止时,窗口不响应鼠标和按键的输入,输入允许时,窗口接受所有的输入。

BOOL EnableWindow(

  HWND hWnd,//处理要启用或禁用的窗口。

  BOOL bEnable//布尔值,指定是启用还是禁用窗口。如果此参数为TRUE,则启用窗口。如果参数为FALSE,则禁用该窗口。

);

*/

Resource.h

#define IDC_MAIN_EDIT                   1001

#define IDC_NOTIFY_ID                   1002

#define IDC_NOTIFY_MESSAGE               1003

#define IDC_NOTIFY_SUCCESSFUL            1004

#define IDC_NOTIFY_ABORTED                1005

#define IDC_NOTIFY_FAILURE              1006

#define IDC_NOTIFY_SUPERSEDED             1007

#define IDC_ERROR_STRING                  1008

#define IDC_RETURN_STRING                 1009

// 新对象的下一组默认值

//

#ifdef APSTUDIO_INVOKED

#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE            102

#define _APS_NEXT_COMMAND_VALUE              40001

#define _APS_NEXT_CONTROL_VALUE            1006

#define _APS_NEXT_SYMED_VALUE                101

#endif

#endif

TESTMCI.rc

/

//

// Dialog

//

TESTMCI DIALOGEX 0, 0, 279, 290

STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "MCI Tester"

FONT 8, "MS Shell Dlg", 400, 0, 0x1

BEGIN

    DEFPUSHBUTTON   "OK",IDOK,61,266,50,14

    PUSHBUTTON      "Close",IDCANCEL,146,266,50,14

    EDITTEXT        IDC_MAIN_EDIT,16,15,248,101,ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL

    LTEXT           "Return String:",IDC_STATIC,19,118,46,8

    EDITTEXT        IDC_ERROR_STRING,144,130,120,67,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_GROUP | NOT WS_TABSTOP

    LTEXT           "Error String:",IDC_STATIC,145,119,40,8

    EDITTEXT        IDC_RETURN_STRING,18,129,120,66,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_GROUP | NOT WS_TABSTOP

    GROUPBOX        "MM_MCINOTIFY message",IDC_STATIC,18,199,247,62

    LTEXT           "",IDC_NOTIFY_ID,30,214,228,8

    LTEXT           "MCI_NOTIFY_SUCCESSFUL",IDC_NOTIFY_SUCCESSFUL,34,230,88,8,WS_DISABLED

    LTEXT           "MCI_NOTIFY_ABORTED",IDC_NOTIFY_ABORTED,163,230,78,8,WS_DISABLED

    LTEXT           "MCI_NOTIFY_FAILURE",IDC_NOTIFY_FAILURE,163,244,74,8,WS_DISABLED

    LTEXT           "MCI_NOTIFY_SUPERSEDED",IDC_NOTIFY_SUPERSEDED,34,244,89,8,WS_DISABLED

END

/

//

// DESIGNINFO

//

#ifdef APSTUDIO_INVOKED

GUIDELINES DESIGNINFO

BEGIN

    "TESTMCI", DIALOG

    BEGIN

        LEFTMARGIN, 7

        RIGHTMARGIN, 272

        TOPMARGIN, 7

        BOTTOMMARGIN, 283

    END

END

#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) 资源

/

       运行结果:

图23-1 交互输入MCI命令

 

总结

实例TESTMCI.C使用了两个最重要的多媒体函数:mciSendString和mciGetErrorString。在 TESTMCI的主编辑窗口中输入字符串后,按下Enter键(或OK按钮),该程序会将输入的字符串作为mciSendString函数的第一个参数:

error = mciSendString(szCommand, szReturn,sizeof(szReturn)/ sizeof(TCHAR), hwnd);

如果编辑窗口中被选定的文本多于一行,该程序将把它们按顺序发送到mciSendString函 数。第二个参数是一个字符串地址。该字符串含有函数返回的信息。程序将些信息显示在窗口的Return String区域。mciSendString返回的错误代码被传递给mciGetErrorString函数。从而得到错误的文本说明,这会被显示在TESTMCI窗口的Error String区域。

●TESTMCI 和 CD 音频

通过控制CD-ROM驱动器来播放音频CD,可以很好地了解MCI命令字符串。因为这些命令字符串通常十分简单,而且你还可以顺便听听音乐。为了方便这个实验,手头上最好备有MSDN中对MCI命令字符串的引用文档,可以参见本书的源代码文件。

请确保CD-ROM驱动器的音频输出连接到扬声器或耳机上,然后放入一张音频CD(例 如Bruce Springsteen的专辑Born to Rim)。在Windows 98 T. CD播放器应用程序可能会自 动启动,并开始播放该专辑。如果是这样的话,关闭CD播放器。然后运行TESTMCI程序:并输入如下命令:

open cdaudio

按下Enter键。单词“open”是一个MCI命令,而单词“cdaudio”是设备名,MCI将其识别为CD-ROM驱动器。(这里假设你的系统中只有一个CD-ROM驱动器:要得到多个 CD-ROM驱动器的名字,则需要使用sysinfo命令。)

TESTMCI的Return String区域显示了 mciSendString函数中系统返回给你程序的字符串。如果open命令运行成功,这里就是一个简单的数字1。TESTMCI的Error String区域显示的是mciGetErrorString根据mciSendString返回值计算出的结果。如果mciSendString没有返回错误代码,则Error String区域将显示文本“The specified command was carried out.”(指定的命令运行正常)。

假设open命令运行正常,现在可以输入如下命令:

play cdaudio

CD就会开始播放该专辑的第一首歌Thunder Road。你可以输入如下命令暂停CD播放:

pause cdaudio

对cdaudio设备来说,也可以使用如下命令暂停CD播放,效果和前面的语句一样:

stop cdaudio

然后可以使用如下语句继续播放:

play cdaudio

到目前为止,我们用过的所有字符串都是由一个命令和一个设备名称组成的。有些命令还有一些选项。例如,输入如下命令:

status cdaudio position

根据播放时间的长短,Return String区域会显示类似如下的数字串:

01:15:25

这是什么意思呢?这显然不是小时、分钟和秒数,因为一张CD没有那么长的时间。为了 找出这个时间格式到底是什么,输入如下命令:

status cdaudio time format

Return String区域现在会显示如下字符串:

这表示“分-秒-帧“ (minute-second-frame)。在CD音频中,每秒有75帧。时间格式中的 “帧”的部分可以取值0~74。

status命令有许多选项。可以使用如下命令得到msf格式的CD的整个长度:

status cdaudio length

以专辑《Bom to Run》来说,Return String区域将显示如下数字:

39:28:19

这表示39分28秒19帧。

现在尝试如下命令:

status cdaudio number of tracks

此时Return String区域会显示如下数字:

8

从该CD封面可以知道,专辑上的Born   歌曲在CD的第五条音轨上。MCI命令中,音轨号码从1开始编号。所以我们可以输入如下命令来找出Bom to Run这首歌有多长:

status cdaudio length track 5

运行该命令后,Return String区域会显示如下数字:

04:30:22

我们也可以使用如下命令找出这条音轨从专辑的什么位置开始:

status cdaudio position track 5

此时Return String区域会显示如下数字:

17:36:35

有了这些信息,现在可以使用如下命令直接跳到专辑中该歌曲所在的音轨了:

play cdaudio from 17:36:35 to 22:06:57

此命令将播放这一符歌曲,然后停止。最后面的这个值是17:36:35加上4:30:22(音轨的长 度)计算出来的。或者,也可以使用如下命令来确定此值:

scacus cdaudio position track 6

或者你还可以用下而的命令把时间格式设置为“音轨-分-秒-帧”:

sec cdaudio time format tmsf

然启运行命令

play cdaudio from 5:0:0:0 co 6:0:0:0

还有一种更简节的方法,命令如下: play cdaudio from 5 to 6

当时间尾部的部分是0的时候,可以省略它们。另外还可以把时间格式设置为以毫秒为单位。

每个MCI命令字符串都是以在串的末尾添加wait选项或者notify选项(或者两者都加 上)。例如,假设只想播放歌曲的最初10秒,此后希望该程序做些其他事情,那么这里提供了一种方式来实现这一点(假定时间格式已被设置为tmsf),命令如下:

Play cdaudio from 5:0:0 to 5:0:10 wait

在这种情况下,mciSendString函数会一直等到函数执行完毕才返回,也就是说,直到Sorn to Run的前10秒播放完成。

很显然,对一般的单线程应用程序来说,这不是一件好事。如果你不小心输入了

play cdaudio wait

那么mciSendString函数将不会把控制权交还给程序,直到播放完整张专辑。如果必须使用 wait选项(这在运行自动MCI脚本时十分方便,我很快会演示),那么请先使用break命令。 此命令允许设置一个可以中断mciSendString命令的虚拟键代码,以将控制权返还给程序。 例如,如果想设置 Esc键作为中断键,可以使用如下命令:

break cdaudioon 27

其中27是VK_ESCAPE的十进制值。

比wait选项更好的选择是notify选项:

play cdaudio from 5:0:0 to 5:0:10 notify

在这种情况下 mciSendString函数会立即返回,但是,当MCI命令中指定的操作结束后, mciSendString的最后一个参数所指定的句柄代表的窗口将收到一条MM_MCINOTIFY消 息。TESTMCI程序将该消息的结果显示在MM_MCINOTIFY组合框内。为了避免和输入 的其他命令混淆,5秒后TESTMCI程序会停止显示MM_MCINOTIFY消息的结果。

你可以同时使用wait和notify选项,但是我想不出这么做的理由。如果不使用这两个选项,默认的行为是既不等待也不通知,通常这是想要的行为。

当结束摆弄这些命令后,可以通过输入如下命令来停止播放CD:

stop cdaudio

如果在关闭之前不停止CD-ROM设备的话,CD会继续播放,哪怕已经关闭了该设备。

还可以尝试一下以下命令,不过你的硬件不一定支持:

eject cdaudio

最后使用如下命令关闭设备: close cdaudio

虽然TESTMCI本身无法保存或加载文本文件,但是你可以在编辑控件和剪贴板之间复制文本。可以在TESTMCI中选择一些文本,复制到剪贴板(使用Ctrl+C),再从剪贴板复制文本到记事本,最后保存。将这个过程反过来,就可以加载一系列MCI命令到TESTMCI 中。如果选中了一系列命令并单击OK按钮或按下Enter键,TESTMCI将逐条执行这些命令。这样就可以构建MCI “脚本”,也就是MCI命令的列表。

例如,假设你想要听这几首歌:「Jungleland」在该专辑的最后一条音轨上,Thunder Road 和Born to Run,并想以以上顺序播放这三首歌曲。你可以创建如下的脚本:

open cdaudio

set cdaudio time format tmsf

break cdaudio on 27

play cdaudio from 8 wait

play cdaudio from 1 to 2 wait

play cdaudio from 5 to 6 wait

stop cdaudio

eject cdaudio

close cdaudio

如果这里没有使用wait关键字,那么脚本将无法正常工作,因为mciSendString命令会立即返回,然后马上执行下一条命令。

现在,应该相当淸楚如何构建一个模拟CD播放器的简单应用程序了。该程序可以判断音轨的数量和每条音轨的长度,并允许用户从任意一点开始播放。(但是请记住 mciSendString总是以文本字符串的形式返回信息,所以需要编写解析逻辑来将这些字符串 转换为数字。)这样的一个程序几乎肯定还会用到Windows计时器.用来计量一秒(或其他长度)时间间隔。在WMJHMER消息中,程序将会调用如下命令来判断CD是处于暂停状态还是播放状态:

scatus cdaudio mode

而如下命令将使程序刷新其显示,以告知用户当前的播放位置:

status cdaudio position

但还有—些更有趣的东西也是可以实现的:如果程序知道音乐的高潮部分的时间点,它就可以在屏蒱上显示与CD音乐同步的图形。无论是用于音乐教学,还是创建自己的图形化音乐视频,这都十分好用。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值