学习笔记-C语言关于waveout接口的使用(一)

写点学习过程中学到的,也写点初学者方便学的,至少对于我来讲是方便的

想用waveout接口主要需要这几个函数:

        打开设备接口 waveOutOpen;

        准备缓冲区 waveOutPrepareHeader;

        向设备写入缓冲区数据 waveOutWrite;

        清理缓冲区 waveOutUnprepareHeader;

        关闭设备接口 waveOutClose;

首先关于打开设备接口的 

waveOutOpen( LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen );

具体请参考:waveOutOpen 函数 (mmeapi.h) - Win32 apps | Microsoft Learn

由于我们现在也不使用CALLBACK回调函数,所以只需要管前三项参数就行了

第一个参数phwo为指向缓冲区的指针,该缓冲区接收标识打开的波形音频输出设备的句柄,我们直接HWAVEOUT device就行,函数执行完会往device里放入打开设备的句柄。

第二个参数uDeviceID是要打开的波形音频输出设备的标识符。我们可以用WAVE_MAPPER来让设备自动选择合适的波形音频输出设备。

第三个pwfx参数是指向 WAVEFORMATEX 结构的指针,该结构标识要发送到设备的波形音频数据的格式。具体请参考WAVEFORMATEX 结构 (Windows) |Microsoft 学习

这个结构体的内容需要我们自己设置,或者你也可以读取并解析完文件后再把格式复制过去。我们在这这么写:

	WAVEFORMATEX wfx;			//结构体初始化设置

	wfx.nSamplesPerSec = 44100 ;	    //采样频率
	wfx.wBitsPerSample = 16;		    //采样位深
	wfx.nChannels = 2;			        //音道
	wfx.cbSize = 0;					    //附加信息
	wfx.wFormatTag = WAVE_FORMAT_PCM;   //PCM编码格式,也可以赋1
	wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;    //单个数据块长度
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;  //数据传输速率

你会发现其实只需要设置采样频率,位深和声道就行了,没错,就是这么简单。

自此,waveOutOpen函数的设置就完成了,完整版为:

	HWAVEOUT device;			        //设备句柄
	WAVEFORMATEX wfx;			        //结构体初始化设置

	wfx.nSamplesPerSec = 44100;	        //采样频率
	wfx.wBitsPerSample = 16;		    //采样位深
	wfx.nChannels = 2;			        //音道
	wfx.cbSize = 0;					    //附加信息
	wfx.wFormatTag = WAVE_FORMAT_PCM;   //PCM编码格式,也可以赋1
	wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

	//尝试打开设备,WAVE_MAPPER是mmsystem.h中定义的常量,指向系统上的默认波形设备
	if (waveOutOpen(&device,WAVE_MAPPER,&wfx,0,0,CALLBACK_NULL)!=MMSYSERR_NOERROR)
	{
		fprintf(stderr, "unable to open WAVE_MAPPER device\n");
		ExitProcess(1);
	}
	printf("The Wave Mapper device was opened successfully!\n");

接下来是向设备写入,这一部分需要用waveOutPrepareHeader;waveOutWrite;waveOutUnprepareHeader;这三个函数。

首先要准备缓冲区函数waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);具体参考:waveOutPrepareHeader 函数 (mmeapi.h) - Win32 apps | Microsoft Learn

第一个参数hwo就是我们之前让waveOutOpen写好的device了。

第二个参数pwh为指向 WAVEHDR结构的指针,该结构标识要准备的数据块。这个结构体成员非常多,这里就不详解了,大伙想了解点链接看就行。这里由于我们目标是能播放就行,所以其实只需要设置结构体中两个成员参数就够了:LPSTR  lpData DWORD dwBufferLength;前者为缓冲区数据的头指针,后者为缓冲区数据长度。我们此时可以打开要播放的音乐文件来准备这两个参数,由于是刚开始,我们就直接读取完整个文件吧,后面再考虑如何一次只读取一小部分。所以可以这么写:

//读取音频文件数据
char* loadAudioBlock(char filename[], unsigned long* buffsize)
{
	FILE* file;
	unsigned long filesize = 0;				//文件大小
	char* data = NULL;						//块地址

	//打开文件
	printf("%s\n", filename);
	file = fopen(filename, "rb+");
	if (file == NULL)
	{
		printf("无法打开文件\n");
		return 0;
	}

	fseek(file, 0, SEEK_END);              //获取文件大小
	filesize = ftell(file) - 44;
	data = (unsigned char*)malloc(filesize);
	fseek(file, 44, SEEK_SET);            //读取头文件后的数据部分
	fread(data, sizeof(unsigned char), filesize, file);

	*buffsize = filesize;                //缓冲区长度为文件大小

	fclose(file);
	return (char*)data;                  //将申请的内存的头指针传递出去
}

第三个参数为WAVEHDR结构体的大小,sizeof(WAVEHDR)就行,没什么好说的。

后面的waveOutWritewaveOutUnprepareHeader格式和waveOutPrepareHeader一模一样,在此就不赘述了。至此,我们关于设备写入数据并输出部分的完整代码为:

	WAVEHDR wave_buff;			//wave缓存区设置	
    LPSTR buff;				//缓冲区指针
	DWORD buffsize;			//缓冲区大小

	//将文件内容输入缓存并把缓存输出到设备
	if ((buff = loadAudioBlock("text.wav",&buffsize)) == NULL)
	{
		fprintf(stderr,"Unable to load file\n");
		return 0;
	}

	wave_buff.dwBufferLength = buffsize;
	wave_buff.lpData = buff;

	//准备要播放的buff
	waveOutPrepareHeader(device, &wave_buff, sizeof(WAVEHDR));
	// 将buff写入设备。waveOutWrite 会立即返回除非使用同步驱动程序(不经常)。
	waveOutWrite(device, &wave_buff, sizeof(WAVEHDR));
	// 等待buff播放完后清理缓冲区
	Sleep(500);
	while (waveOutUnprepareHeader(device, &wave_buff, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
		Sleep(100);

如果你不在意后面的内存释放,设备关闭之类的小细节,那么我们此时已经完成了全部工作了。

整个项目完整代码为(当然,我还是在意上面提到的那些细节的):

#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib,"Winmm.lib")

//读取音频文件数据
char* loadAudioBlock(char filename[], unsigned long* buffsize)
{
	FILE* file;
	unsigned long filesize = 0;				//文件大小
	char* data = NULL;						//块地址

	//打开文件
	printf("%s\n", filename);
	file = fopen(filename, "rb+");
	if (file == NULL)
	{
		printf("无法打开文件\n");
		return 0;
	}

	fseek(file, 0, SEEK_END);
	filesize = ftell(file) - 44;
	data = (unsigned char*)malloc(filesize);
	fseek(file, 44, SEEK_SET);
	fread(data, sizeof(unsigned char), filesize, file);

	*buffsize = filesize;

	fclose(file);
	return (char*)data;
}

int main()
{
	HWAVEOUT device;			    //设备句柄
	WAVEFORMATEX wfx;			    //结构体初始化设置
	WAVEHDR wave_buff;			    //wave缓存区设置
	LPSTR buff;				        //缓冲区指针
	DWORD buffsize;			        //缓冲区大小

	wfx.nSamplesPerSec = 44100 ;	//采样频率
	wfx.wBitsPerSample = 16;		//采样位深
	wfx.nChannels = 2;		    	//音道
	wfx.cbSize = 0;					//附加信息
	wfx.wFormatTag = WAVE_FORMAT_PCM;      //PCM编码格式,也可以赋1
	wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

	if (waveOutOpen(&device, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR)
	{
		fprintf(stderr, "unable to open WAVE_MAPPER device\n");
		return 0;
	}
	printf("The Wave Mapper device was opened successfully!\n");

	//将文件内容输入缓存并把缓存输出到设备,文件放在和代码同文件夹内
	if ((buff = loadAudioBlock("text.wav", &buffsize)) == NULL)
	{
		fprintf(stderr, "Unable to load file\n");
		return 0;
	}

	wave_buff.dwBufferLength = buffsize;
	wave_buff.lpData = buff;

	//准备要播放的buff
	waveOutPrepareHeader(device, &wave_buff, sizeof(WAVEHDR));
	// 将buff写入设备。waveOutWrite 会立即返回除非使用同步驱动程序(不经常)。
	waveOutWrite(device, &wave_buff, sizeof(WAVEHDR));
	// 直到buff播放完毕,然后开始清理缓冲区
	Sleep(500);
	while (waveOutUnprepareHeader(device, &wave_buff, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
		Sleep(100);

	waveOutClose(device);
	free(buff);
	return 0;
}

按道理来讲,放好音乐文件后直接运行就可以播放了,至少我是可以成功运行的,有什么问题再私信我吧,反正这是学习笔记,有问题的地方肯定还是有很多的。作为初学者,在刚开始接触waveOut的时候不可谓不头疼,晦涩难懂的代码,全是搬运的内容,完全无法运行的下载的代码以及清一色严格的匈牙利命名法,都让学习寸步难行,好在最后还是硬啃读下来了,不得不说,这段时间学到了很多。好!最后希望与各位共勉吧。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
很好的问题!让我来解答。 首先,让我们来了解一下 `Comparable` 接口。这个接口是 Java 中的一个接口,它只有一个方法 `compareTo()`。这个方法的作用是定义类的自然排序顺序。 如果我们要对一个对象集合进行排序,只需要让这个类实现 `Comparable` 接口,并且重写 `compareTo()` 方法。这个方法返回一个整型值,表示该对象与另一个对象的大小关系。如果该对象小于另一个对象,则返回负整数;如果两个对象相等,则返回零;如果该对象大于另一个对象,则返回正整数。 下面是一个简单的例子,展示如何使用 `Comparable` 接口进行排序。 ```java public class Student implements Comparable<Student> { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } // 重写 compareTo 方法 @Override public int compareTo(Student o) { // 按照年龄从小到大排序 return this.age - o.getAge(); } } ``` 在这个例子中,我们定义了一个 `Student` 类,并且让它实现了 `Comparable` 接口。我们重写了 `compareTo()` 方法,按照年龄从小到大排序。 现在,我们可以使用 `Collections.sort()` 方法对 `Student` 对象进行排序了: ```java List<Student> students = new ArrayList<>(); students.add(new Student("Tom", 20)); students.add(new Student("Jerry", 18)); students.add(new Student("Alice", 22)); Collections.sort(students); for (Student student : students) { System.out.println(student.getName() + " " + student.getAge()); } ``` 输出结果为: ``` Jerry 18 Tom 20 Alice 22 ``` 可以看到,我们成功地按照年龄从小到大对 `Student` 对象进行了排序。 希望这个例子能够帮助你理解如何使用 `Comparable` 接口进行排序。如果你还有其他问题,请随时提出!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值