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的声调变化。