构建PCM格式WAV声音文件

WAV声音文件是Windows系统早期就开始使用的一种声音文件格式,可以包含多种音频格式。其中PCM格式属于未压缩音频数据流,数据格式比较简单。

下面就生成PCM格式音频文件进行说明。

PCM格式音频文件包含三个部分:RIFF信息头,FORMAT信息头和DATA数据体,按照小端字节序存储。DATA数据体包含单声道和双声道,8位、16位、32位的音频数据,其中双声道按照左右左右左右的顺序,依次存放左右声道的采样数据。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>

#define SIZE_RIFF   (12)
#define SIZE_FORMAT (24)
#define SIZE_DATA   (8)
#define SIZE_WAVE   (SIZE_RIFF + SIZE_FORMAT + SIZE_DATA)

#define ID_RIFF     (0x46464952) // 'RIFF'
#define ID_WAVE     (0x45564157) // 'WAVE'
#define ID_FMT      (0x20746D66) // 'fmt '
#define ID_DATA     (0x61746164) // 'data'

struct st_riff
{
	uint32_t id;    // 'RIFF' (0x52494646)
	uint32_t size;  // fileSize - 8
	uint32_t type;  // 'WAVE'(0x57415645)
};

struct st_format
{
	uint32_t id;    // 'fmt ' (0x666D7420)
	uint32_t size;  // 16
	uint16_t audio_format;  // 1 for PCM
	uint16_t num_channels;  // 1 for mono, 2 for stereo
	uint32_t sample_rate;
	uint32_t byte_rate;     // N = sample_rate * num_channels * bits_per_sample / 8
	uint16_t block_align;   // N =               num_channels * bits_per_sample / 8
	uint16_t bits_per_sample;   // 8 or 16 or 32
};

struct st_data
{
	uint32_t id;    // 'data' (0x64617461)
	uint32_t size;  // N = byte_rate * seconds
	void *data;  // audio data
};

struct st_wave
{
	struct st_riff riff;
	struct st_format format;
	struct st_data data;
};

enum e_pcm_type
{
	MONO_8BIT,
	MONO_16BIT,
	STEREO_8BIT,
	STEREO_16BIT,
};

struct st_wave * create_pcm_wave(int pcm_type, uint32_t sample_rate, uint32_t seconds)
{
	struct st_wave * wave = NULL;
	
	uint32_t wave_size;
	uint32_t data_size;
	
	uint16_t num_channels;
	uint16_t bits_per_sample;

	do
	{
		switch(pcm_type)
		{
		case MONO_8BIT:
			num_channels = 1;
			bits_per_sample = 8;
			break;
		case MONO_16BIT:
			num_channels = 1;
			bits_per_sample = 16;
			break;
		case STEREO_8BIT:
			num_channels = 2;
			bits_per_sample = 8;
			break;
		case STEREO_16BIT:
			num_channels = 2;
			bits_per_sample = 16;
			break;
		default:
			num_channels = 0;
			bits_per_sample = 0;
			break;
		}
		
		if (num_channels == 0)
			break;
		
		data_size = seconds * sample_rate * num_channels * bits_per_sample / 8;
		wave_size = sizeof(struct st_wave) + data_size;
		
		wave = (struct st_wave *)malloc(wave_size);
		if (wave == NULL)
			break;

		wave->riff.id = ID_RIFF;   // 'RIFF'
		wave->riff.size = (SIZE_WAVE - 8) + data_size;
		wave->riff.type = ID_WAVE; // 'WAVE'

		wave->format.id = ID_FMT; // 'fmt '
		wave->format.size = (SIZE_FORMAT - 8);
		wave->format.audio_format = 1;  // 1 for PCM
		wave->format.num_channels = num_channels;  // 1 or 2
		wave->format.sample_rate = sample_rate;
		wave->format.byte_rate = sample_rate * num_channels * bits_per_sample / 8;
		wave->format.block_align = num_channels * bits_per_sample / 8;
		wave->format.bits_per_sample = bits_per_sample;   // 8 or 16 or 32

		wave->data.id = ID_DATA; // 'data'
		wave->data.size = data_size;
		wave->data.data = (uint8_t *)wave + sizeof(struct st_wave);

	} while(0);
	
	return wave;
}

void write_pcm_wave(const struct st_wave * wave, const char *path)
{
	FILE *file = NULL;

	do
	{
		file = fopen(path, "wb");
		if (file == NULL)
			break;

		fwrite(&(wave->riff.id), 4, 1, file);
		fwrite(&(wave->riff.size), 4, 1, file);
		fwrite(&(wave->riff.type), 4, 1, file);
		fwrite(&(wave->format.id), 4, 1, file);
		fwrite(&(wave->format.size), 4, 1, file);
		fwrite(&(wave->format.audio_format), 2, 1, file);
		fwrite(&(wave->format.num_channels), 2, 1, file);
		fwrite(&(wave->format.sample_rate), 4, 1, file);
		fwrite(&(wave->format.byte_rate), 4, 1, file);
		fwrite(&(wave->format.block_align), 2, 1, file);
		fwrite(&(wave->format.bits_per_sample), 2, 1, file);
		fwrite(&(wave->data.id), 4, 1, file);
		fwrite(&(wave->data.size), 4, 1, file);
		fwrite(wave->data.data, wave->data.size, 1, file);
		
	} while(0);
	
	if (file)
		fclose(file);
}

构建一段8秒钟的8K采样率PCM单声道8BIT音频数据,每秒钟播放一个音高的声音,其中频率信息参考如下c1、d1、e1、f1、g1、a1、b1和c2:

void gen_mono_8bit_sine_wave(const struct st_wave * wave, const double freq_hz[], size_t freq_size)
{
	uint32_t data_index;
	double time;
	double value;
	double freq;
	uint32_t freq_index;
	uint8_t *data = (uint8_t *)(wave->data.data);

	for(data_index = 0; data_index < wave->data.size; data_index++)
	{
		freq_index = data_index / wave->format.sample_rate;
		if (freq_index >= freq_size)
			freq_index = freq_size - 1;
		freq = freq_hz[freq_index];

		time = (double)data_index / wave->format.sample_rate;

		value = 0.5 + sin(2 * M_PI * freq * time) / 2;
		data[data_index] = (uint8_t)(value * 255);
	}
}

int main()
{
	struct st_wave * wave = NULL;

	const double note_freq[8] = 
	{
		261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25
	};

	do
	{
		wave = create_pcm_wave(MONO_8BIT, 8000, 8);
		if (wave == NULL)
			break;

		gen_mono_8bit_sine_wave(wave, note_freq, 8);

		write_pcm_wave(wave, "sample.wav");

	} while(0);

	if (wave)
		free(wave);

	return 0;
}

编译运行后,生成一个8秒钟音频文件,通过HxD十六进制编辑器查看文件,框选部分为文件头,后续连续存放着数据部分。

通过Media Player播放该文件,可以听到do re mi fa sol la xi do的声调变化。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值