【C语言】Wave文件处理
WAVE 文件作为多媒体中使用的声音波形文件格式之一,它是以RIFF(Resource Interchange File Format)格式为标准的。
1. 大小端对齐问题
大小端是不同的对于数据在内存地址中的存放方式,不同的处理器(平台)的数据存储方式是不同。
如果实现跨平台通信则大小端是不能忽视的问题。
- 大端模式:数据的高位存储在内存的低字节。ARM/PowerPC等处理器采用大端模式
- 小端模式:数据的地位存储在内存的低字节。Intel架构处理器采用小端模式。
如一个数据:0x12345678,其对应内存地址是0x00~0x03。
大端模式 | 小端模式 | |||||||
地址 | 0x00 | 0x01 | 0x02 | 0x03 | 0x00 | 0x01 | 0x02 | 0x03 |
数据 | 0x12 | 0x34 | 0x56 | 0x78 | 0x78 | 0x56 | 0x34 | 0x12 |
字节序对应着内存的存储(组织)模式,如网络字节序采用大端模式。字节序只是对内置的数据类型而言(int、short、double、long....),而对于char而言由于其本身只有一个字节则字节序和存储模式对其不影响。因此,字符串在跨平台传输时不用考虑字节序。单字节内存中的比特为不受字节序的影响,大于1个字节的数据类型,字节序才对其有影响。更多内容,请移步:关于大小端问题和字节序问题的一些总结。
//大小端数据的转换
char* reverseData(char* d,int len)
{char *reversed = (char*) malloc(sizeof(char)*len);for (int i = 0; i < len; i++) {reversed[i] = d[len-1-i];}return reversed;
}
大小端数据的转换,具体请移步至:C语言二进制文件读写以及大小端转换 。
2. Wave 文件格式
2.1 块结构
块结构(chunk)
是RIFF文件的基本单元,其基本结构如下:
struct chunk
{uint32_t id; // 块标志uint32_t size; // 块大小uint8_t data[size]; // 块数据
};
- id 4字节,用以标识块中所包含的数据。如:RIFF,LIST,fmt,data,WAV,AVI等,由于这种文件结构最初是由Microsoft和IBM为PC机所定义,RIFF文件是按照小端 little-endian字节顺序写入的;
- size 块大小存储在data域中的数据长度,不包含id和size的大小;
- data 包含数据,数据以字为单位存放,如果数据长度为奇数(字节为单位),则最后添加一个空字节。
chunk
是可以嵌套的,但是只有块标志为RIFF或者LIST的chunk才能包含其他的chunk。
一个WAV文件通常有三个chunk以及一个可选chunk,其在文件中的排列方式依次是:RIFF chunk,Format chunk,Fact chunk(附加块,可选),Data chunk。
2.2 RIFF 块
- id:FOURCC 值为'R' 'I' 'F' 'F'
- size:其data字段中数据的大小 字节数
- data:包含其他的chunk
2.3 Format 块
- id:FOURCC 值为 'f' 'm' 't' ' '
- size:数据字段包含数据的大小。如无扩展块,则值为16;有扩展块,则值为= 16 + 2字节扩展块长度 + 扩展块长度或者值为18(只有扩展块的长度为2字节,值为0)
- data:存放音频格式、声道数、采样率等信息,如:
- format_tag:2字节,表示音频数据的格式。如值为1,表示使用PCM格式。
- channels:2字节,声道数。值为1则为单声道,为2则是双声道。
- samples_per_sec:采样率,主要有22.05KHz,44.1kHz和48KHz。
- bytes_per sec:音频的码率,每秒播放的字节数。samples_per_sec * channels * bits_per_sample / 8,可以估算出使用缓冲区的大小
- block_align:数据块对齐单位,一次采样的大小,值为声道数 * 量化位数 / 8,在播放时需要一次处理多个该值大小的字节数据。
- bits_per_sample:音频sample的量化位数,有16位,24位和32位等。
- cbSize:扩展区的长度
- 扩展块内容:22字节。
在Format块中一个比较主要的字段就是format_tag,它表示音频数据是以何种方式编码存放的:
- 0x0001:WAVE_FORMAT_PCM,采用PCM格式
- 0x0003:WAVE_FORMAT_IEEE_FLOAT,存放的值为IEEE float,范围为[-1.0f,1.0f]
- 0x0006:WAVE_FORMAT_ALAW , 8bit ITU-T G.711 A-law
- 0x0007:WAVE_FORMAT_MULAW,8bit ITU-T G.711 μμ-law
- 0XFFFE:WAVE_FORMAT_EXTENSIBLE,具体的编码方式有扩展区的 sub_format 字段决定
当WAV文件使用的不是PCM编码方式是,就需要扩展格式块。
扩展格式块是在基本的Format 块中添加一段数据。该数据的前两个字节,表示的扩展块的长度。紧接其后的是扩展的数据区,含有扩展的格式信息,其具体的长度取决于压缩编码的类型。当某种编码方式(如 ITU G.711 a-law)使扩展区的长度为0,扩展区的长度字段还必须保留,只是其值设置为0。
扩展区的各个字节的含义如下:
- size: 2字节,扩展区的数据长度 ,可以为0或22
- valid_bits_per_sample: 2字节,有效的采样位数,最大值为采样字节数 * 8。可以使用更灵活的量化位数,通常音频sample的量化位数为8的倍数,但是使用了WAVE_FORMAT_EXTENSIBLE时,量化的位数有扩展区中的
valid bits per sample
来描述,可以小于Format chunk中制定的bits per sample
。 - channle mask: 4字节,声道掩码
- sub format:16字节,GUID,include the data format code,数据格式码。
在Format块中的format_tag设置为0xFFFE时,表示使用扩展区中的sub_format来决定音频的数据的编码方式。在以下几种情况下必须要使用WAVE_FORMAT_EXTENSIBLE
- PCM数据的量化位数大于16
- 音频的采样声道大于2
- 实际的量化位数不是8的倍数
- 存储顺序和播放顺序不一致,需要指定从声道顺序到声卡播放顺序的映射情况。
2.4 Fact 块(可选)
- id:FOURCC 值为 'f' 'a' 'c' 't'
- size:数据域的长度,4(最小值为4)
- 采样总数:4字节
2.5 Data 块
- id:FOURCC 值为'd' 'a' 't' 'a'
- size:数据域的长度
- data:具体的音频数据存放在这里
Data chunk块中存放的是音频的采样数据。每个sample按照采样的时间顺序写入,对于使用多个字节的sample,使用小端模式存放(低位字节存放在低地址,高位字节存放在高地址)。对于多声道的sample采用交叉存放的方式。例如:立体双声道的sample存储顺序为:声道1的第一个sample,声道2的第一个sample;声道1的第二个sample,声道2的第二个sample;依次类推....。对于PCM数据,有以下两种的存储方式:
- 单声道,量化位数为8,使用偏移二进制码
- 除上面之外的,使用补码方式存储。
采用压缩编码的WAV文件,必须要有Fact chunk,该块中只有一个数据,为每个声道的采样总数。
详细内容,请移步:RIFF和WAVE音频文件格式
3. 读取Wave文件
需要注意的是,WAVE 文件虽然可以压缩,但一般都使用不压缩的格式。常见的格式为:44.1KHz 采样率、16Bit的分辨率、双声道。所以,WAVE可以保存音质要求非常高的声音文件。但这种文件的体积也非常大,以 44.1KHz 16bit 双声道的数据为例,一分钟的声音数据量为:4100*2byte*2channel*60s/1024/1024=10.09M 。所以,不合适在网上传送。
本节中,笔者想要通过一个数据结构表示Wave文件的格式,以方便Wave文件的读取。由于不同软件录制的Wave文件的格式可能略有不同,为了更有针对的解释,笔者这里先向大家推荐一个小巧的音频处理程序 WaveCN,可以录制不同格式的音频:
推荐一个C语言下的智能提示插件:VAssistX,当使用.时,结构体中的成员就会自动列出来,是不是非常地方便。
下面我们具体地分析 WAVE 文件的格式:
字段名 |
字节数 |
描述 |
ChunkID |
4 |
文件头标识,ASCII 码表示的“RIFF”。(0x52494646) |
ChunkSize |
4 |
整个数据文件的大小,36+SubChunk2Size,或是4 + ( 8 + SubChunk1Size ) + ( 8 + SubChunk2Size ),不包括上面ID和Size本身。 |
Format |
4 |
ASCII 码表示的" WAVE"。(0x57415645) |
当前偏移量为12 | ||
SubChunk1ID |
4 |
新的数据块(格式信息说明块)。 |
SubChunk1Size |
4 |
本块数据的大小(对于PCM,值为16)。 不包括ID和Size字段本身。 |
AudioFormat |
2 |
音频的格式说明,PCM = 1 ,表示为线性采样,否则可能是一些压缩形式。 |
NumChannels |
2 |
声道数,声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。 1 => 单声道 | 2 => 双声道 |
SampleRate |
4 |
采样率,如 8000,44100 等值 |
ByteRate |
4 |
比特率,每秒所需要的字节数,SampleRate * numChannels * BitsPerSample / 8 |
BlockAlign |
2 |
数据块对齐单元,NumChannels * BitsPerSample / 8 |
BitsPerSample |
2 |
采样分辨率,也就是每个样本用几位来表示,一般是 8bits 或是 16bits |
当前偏移量为36 | ||
FactID | 4 | ASCII 码表示的“fact”。 |
FactSize | 4 | 数据域的长度,4(最小值为4) |
SampleSize | 4 | 采样总数 |
当前偏移量为48(注意,fact块可选) | ||
SubChunk2ID |
4 |
新数据块(真正的声音数据)。 ASCII 码表示的“data”。(0x64617461) |
SubChunk2Size |
4 |
本数据块的大小,不包括ID和Size字段本身 |
Data |
N |
真正的声音数据 |
对于Data块,根据声道数和采样分辨率的不同情况,布局如下(每列代表8bits):
8 Bit 单声道
采样1 |
采样2 |
数据1 |
数据2 |
8 Bit 双声道
采样1 |
采样2 |
||
声道1数据1 |
声道2数据1 |
声道1数据2 |
声道2数据2 |
16 Bit 单声道
采样1 |
采样2 |
||
数据1低字节 |
数据1高字节 |
数据1低字节 |
数据1高字节 |
16 Bit 双声道
采样1 |
|||
声道1数据1低字节 |
声道1数据1高字节 |
声道2数据1低字节 |
声道2数据1高字节 |
采样2 |
|||
声道1数据2低字节 |
声道1数据2高字节 |
声道2数据2低字节 |
声道2数据2高字节 |
3.1 Wave文件头的数据结构
根据前面的了解,我们知道一个Wave文件的头结构由三个部分组成,下面笔者将介绍如何利用结构体表示对应的数据结构。
3.1.1 RIFF/WAV 文件标识段
typedef struct _wav_riff_t {char id[5]; //ID:"RIFF"int size; //file_len - 8char type[5]; //type:"WAVE"
}wav_riff_t;
3.1.2 声音数据格式说明段
typedef struct _wav_format_t {char id[5]; //ID:"fmt"int size;short compression_code;short channels;int samples_per_sec;int avg_bytes_per_sec;short block_align;short bits_per_sample;
}wav_format_t;
3.1.3 声音数据说明段
typedef struct _wav_fact_t {char id[5];int size;
}wav_fact_t;typedef struct _wav_data_t {char id[5];int size;
}wav_data_t;
3.1.4 整个Wave文件头
typedef struct _wav_t {FILE *fp;wav_riff_t riff;wav_format_t format;wav_fact_t fact;wav_data_t data;int file_size;int data_offset;int data_size;
}wav_t;
3.2 Wave文件操作
3.2.1 打开Wave文件
读取Wave文件的头信息至内存。
//打开一个wave文件,并读取头结构信息到内存
wav_t *wav_open(char *file_name) {wav_t *wav = NULL;char buffer[256];int read_len = 0;int offset = 0;if (NULL == file_name) {printf("file_name is NULL\n");return NULL;}wav = (wav_t *)malloc(sizeof(wav_t));if (NULL == wav) {printf("malloc wav failedly\n");return NULL;}//memset() 函数常用于内存空间初始化。//如:char str[100]; //memset(str,0,100); memset(wav, 0, sizeof(wav_t));wav->fp = fopen(file_name, "r");if (NULL == wav->fp) {printf("fopen %s failedly\n", file_name);free(wav);return NULL;}//handle RIFF WAVE chunk read_len = fread(buffer, 1, 12, wav->fp);if (read_len < 12) {printf("error wav file\n");wav_close(&wav);return NULL;}if (strncasecmp("RIFF", buffer, 4)) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(wav->riff.id, buffer, 4);wav->riff.size = *(int *)(buffer + 4);if (strncasecmp("WAVE", buffer + 8, 4)) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(wav->riff.type, buffer + 8, 4);wav->file_size = wav->riff.size + 8;offset += 12;while (1) {char id_buffer[5] = { 0 };int tmp_size = 0;read_len = fread(buffer, 1, 8, wav->fp);if (read_len < 8) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(id_buffer, buffer, 4);tmp_size = *(int *)(buffer + 4);if (0 == strncasecmp("FMT", id_buffer, 3)) {memcpy(wav->format.id, id_buffer, 3);wav->format.size = tmp_size;read_len = fread(buffer, 1, tmp_size, wav->fp);if (read_len < tmp_size) {printf("error wav file\n");wav_close(&wav);return NULL;}wav->format.compression_code = *(short *)buffer;wav->format.channels = *(short *)(buffer + 2);wav->format.samples_per_sec = *(int *)(buffer + 4);wav->format.avg_bytes_per_sec = *(int *)(buffer + 8);wav->format.block_align = *(short *)(buffer + 12);wav->format.bits_per_sample = *(short *)(buffer + 14);}else if (0 == strncasecmp("DATA", id_buffer, 4)) {memcpy(wav->data.id, id_buffer, 4);wav->data.size = tmp_size;offset += 8;wav->data_offset = offset;wav->data_size = wav->data.size;break;}else {printf("unhandled chunk: %s, size: %d\n", id_buffer, tmp_size);fseek(wav->fp, tmp_size, SEEK_CUR);}offset += 8 + tmp_size;}return wav;
}
3.2.2 关闭Wave文件
清空结构体的信息,同时关闭文件。
void wav_close(wav_t **wav) {wav_t *tmp_wav;if (NULL == wav) {return;}tmp_wav = *wav;if (NULL == tmp_wav) {return;}if (NULL != tmp_wav->fp) {fclose(tmp_wav->fp);}free(tmp_wav);*wav = NULL;
}
3.2.3 移动Wave文件指针
void wav_rewind(wav_t *wav) {if (fseek(wav->fp, wav->data_offset, SEEK_SET) < 0) {printf("wav rewind failedly\n");}
}
3.2.4 判断文件是否读取结束
如果文件结束,则返回非0值,否则返回0。
int wav_over(wav_t *wav) {return feof(wav->fp);
}
3.2.5 读取Wave文件中的数据
int wav_read_data(wav_t *wav, char *buffer, int buffer_size) {return fread(buffer, 1, buffer_size, wav->fp);
}
3.2.6 输出Wave文件信息
void wav_dump(wav_t *wav) {printf("file length: %d\n", wav->file_size);printf("\nRIFF WAVE Chunk\n");printf("id: %s\n", wav->riff.id);printf("size: %d\n", wav->riff.size);printf("type: %s\n", wav->riff.type);printf("\nFORMAT Chunk\n");printf("id: %s\n", wav->format.id);printf("size: %d\n", wav->format.size);if (wav->format.compression_code == 0) {printf("compression: Unknown\n");}else if (wav->format.compression_code == 1) {printf("compression: PCM/uncompressed\n");}else if (wav->format.compression_code == 2) {printf("compression: Microsoft ADPCM\n");}else if (wav->format.compression_code == 6) {printf("compression: ITU G.711 a-law\n");}else if (wav->format.compression_code == 7) {printf("compression: ITU G.711 ?μ-law\n");}else if (wav->format.compression_code == 17) {printf("compression: IMA ADPCM\n");}else if (wav->format.compression_code == 20) {printf("compression: ITU G.723 ADPCM (Yamaha)\n");}else if (wav->format.compression_code == 49) {printf("compression: GSM 6.10\n");}else if (wav->format.compression_code == 64) {printf("compression: ITU G.721 ADPCM\n");}else if (wav->format.compression_code == 80) {printf("compression: MPEG\n");}else {printf("compression: Unknown\n");}printf("channels: %d\n", wav->format.channels);printf("samples: %d\n", wav->format.samples_per_sec);printf("avg_bytes_per_sec: %d\n", wav->format.avg_bytes_per_sec);printf("block_align: %d\n", wav->format.block_align);printf("bits_per_sample: %d\n", wav->format.bits_per_sample);printf("\nDATA Chunk\n");printf("id: %s\n", wav->data.id);printf("size: %d\n", wav->data.size);printf("data offset: %d\n", wav->data_offset);
}
3.2.7 测试方法
int main(int argc, char* argv[])
{wav_t *wav = NULL;wav = wav_open("E:\\test.wav");if (NULL != wav) {wav_dump(wav);wav_close(&wav);}getch();return 1;
}
4. 实例分析
下面我们看一个具体的例子,声音文件如下:
处理程序完整代码实例:
// WaveAns.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include<conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>#ifdef _MSC_VER
#define strcasecmp stricmp
#define strncasecmp strnicmp
#endiftypedef struct _wav_riff_t {char id[5]; //ID:"RIFF"int size; //file_len - 8char type[5]; //type:"WAVE"
}wav_riff_t;typedef struct _wav_format_t {char id[5]; //ID:"fmt"int size;short compression_code;short channels;int samples_per_sec;int avg_bytes_per_sec;short block_align;short bits_per_sample;
}wav_format_t;typedef struct _wav_fact_t {char id[5];int size;int total;//采样总数
}wav_fact_t;typedef struct _wav_data_t {char id[5];int size;
}wav_data_t;typedef struct _wav_t {FILE *fp;wav_riff_t riff;wav_format_t format;wav_fact_t fact;wav_data_t data;int file_size;int data_offset;int data_size;
}wav_t;wav_t *wav_open(char *file_name);void wav_close(wav_t **wav);void wav_rewind(wav_t *wav);int wav_over(wav_t *wav);int wav_read_data(wav_t *wav, char *buffer, int buffer_size);void wav_dump(wav_t *wav);char* reverseData(char* d, int len);//大小端数据的转换
char* reverseData(char* d,int len)
{char *reversed = (char*)malloc(sizeof(char)*len);memset(reversed, 0, len);for (int i = 0; i < len; i++) {reversed[i] = d[len-1-i];}return reversed;
}wav_t *wav_open(char *file_name) {wav_t *wav = NULL;char buffer[256];int read_len = 0;int offset = 0;if (NULL == file_name) {printf("file_name is NULL\n");return NULL;}wav = (wav_t *)malloc(sizeof(wav_t));if (NULL == wav) {printf("malloc wav failedly\n");return NULL;}memset(wav, 0, sizeof(wav_t));wav->fp = fopen(file_name, "r");if (NULL == wav->fp) {printf("fopen %s failedly\n", file_name);free(wav);return NULL;}//handle RIFF WAVE chunk read_len = fread(buffer, 1, 12, wav->fp);if (read_len < 12) {printf("error wav file\n");wav_close(&wav);return NULL;}if (strncasecmp("RIFF", buffer, 4)) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(wav->riff.id, buffer, 4);wav->riff.size = *(int *)(buffer+4);//这里,小段方式存储?if (strncasecmp("WAVE", buffer + 8, 4)) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(wav->riff.type, buffer + 8, 4);wav->file_size = wav->riff.size + 8;offset += 12;while (1) {//IDchar id_buffer[5] = { 0 };//大小int tmp_size = 0;//尝试读取8个字节,即读取ID和Size参数read_len = fread(buffer, 1, 8, wav->fp);if (read_len < 8) {printf("error wav file\n");wav_close(&wav);return NULL;}memcpy(id_buffer, buffer, 4);tmp_size = *(int *)((buffer+4));if (0 == strncasecmp("FMT", id_buffer, 3)) {//数据格式信息说明块memcpy(wav->format.id, id_buffer, 3);wav->format.size = tmp_size;read_len = fread(buffer, 1, tmp_size, wav->fp);if (read_len < tmp_size) {printf("error wav file\n");wav_close(&wav);return NULL;}wav->format.compression_code = *(short *)buffer;//压缩格式,1代表不压缩,线性采样wav->format.channels = *(short *)(buffer + 2);//通道数wav->format.samples_per_sec = *(int *)(buffer + 4);//采样率wav->format.avg_bytes_per_sec = *(int *)(buffer + 8);//每秒所需的字节数wav->format.block_align = *(short *)(buffer + 12);//数据块对齐wav->format.bits_per_sample = *(short *)(buffer + 14);//采样分辨率} else if (0 == strncasecmp("DATA", id_buffer, 4)) {//数据块(最后一个块)memcpy(wav->data.id, id_buffer, 4);wav->data.size = tmp_size;offset += 8;wav->data_offset = offset;wav->data_size = wav->data.size;break;}else if (0 == strncasecmp("fact", id_buffer, 4)) {//fact 块memcpy(wav->fact.id, id_buffer, 4);//字符串不能直接赋值wav->fact.size = tmp_size;//块数据大小wav->fact.total = *(int *)(buffer + 8);//采样个数}else {//无法解析printf("unhandled chunk: %s, size: %d\n", id_buffer, tmp_size);fseek(wav->fp, tmp_size, SEEK_CUR);}//指针后移offset += 8 + tmp_size;}return wav;
}void wav_close(wav_t **wav) {wav_t *tmp_wav;if (NULL == wav) {return;}tmp_wav = *wav;if (NULL == tmp_wav) {return;}if (NULL != tmp_wav->fp) {fclose(tmp_wav->fp);}free(tmp_wav);*wav = NULL;
}void wav_rewind(wav_t *wav) {if (fseek(wav->fp, wav->data_offset, SEEK_SET) < 0) {printf("wav rewind failedly\n");}
}int wav_over(wav_t *wav) {return feof(wav->fp);
}int wav_read_data(wav_t *wav, short *buffer, int size,int num) {return fread(buffer, size, num, wav->fp);
}//输出块信息
void wav_dump(wav_t *wav) {printf("file length: %d\n", wav->file_size);printf("\nRIFF WAVE Chunk\n");printf("id: %s\n", wav->riff.id);printf("size: %d\n", wav->riff.size);printf("type: %s\n", wav->riff.type);printf("\nFORMAT Chunk\n");printf("id: %s\n", wav->format.id);printf("size: %d\n", wav->format.size);if (wav->format.compression_code == 0) {printf("compression: Unknown\n");}else if (wav->format.compression_code == 1) {printf("compression: PCM/uncompressed\n");}else if (wav->format.compression_code == 2) {printf("compression: Microsoft ADPCM\n");}else if (wav->format.compression_code == 6) {printf("compression: ITU G.711 a-law\n");}else if (wav->format.compression_code == 7) {printf("compression: ITU G.711 ?μ-law\n");}else if (wav->format.compression_code == 17) {printf("compression: IMA ADPCM\n");}else if (wav->format.compression_code == 20) {printf("compression: ITU G.723 ADPCM (Yamaha)\n");}else if (wav->format.compression_code == 49) {printf("compression: GSM 6.10\n");}else if (wav->format.compression_code == 64) {printf("compression: ITU G.721 ADPCM\n");}else if (wav->format.compression_code == 80) {printf("compression: MPEG\n");}else {printf("compression: Unknown\n");}printf("channels: %d\n", wav->format.channels);printf("samples: %d\n", wav->format.samples_per_sec);printf("avg_bytes_per_sec: %d\n", wav->format.avg_bytes_per_sec);printf("block_align: %d\n", wav->format.block_align);printf("bits_per_sample: %d\n", wav->format.bits_per_sample);//可选fact块if (stricmp(wav->fact.id, "") != 0) {printf("\nFact Chunk\n");printf("id: %s\n", wav->fact.id);printf("size: %d\n", wav->fact.size);printf("samples: %d\n", wav->fact.total);}printf("\nDATA Chunk\n");printf("id: %s\n", wav->data.id);printf("size: %d\n", wav->data.size);printf("data offset: %d\n", wav->data_offset);
}//正太分布
#define pi 3.1415926
double normal(double x, double miu, double sigma)
{return 1.0 / sqrt(2 * pi) / sigma * exp(-1 * (x - miu)*(x - miu) / (2 * sigma*sigma));
}/*
从正太分布中采样num:采样个数rang:用于确定在几sigma处取值miu:均值sigma:方差
*/
double* getWhight(int num, int rang, double miu, double sigma) {//确定sigma取值double numOfSigma = 0.5 * rang / num;//确定采样的上下限double min = miu - numOfSigma * sigma;double max = miu + numOfSigma * sigma;double* y = (double*)malloc(sizeof(double) * num);memset(y, 0, sizeof(double)*num);//初始化double scale = (max - min) / (num - 1);for (int i = 0; i < num; i++) {double x = min + scale * i;y[i] = normal(x, miu, sigma);}return y;
}int main(int argc, char* argv[])
{wav_t *wav = NULL;//打开wav文件wav = wav_open("E:\\test.wav");if (NULL != wav) {//输出块信息wav_dump(wav);float * data = NULL;int numOfSample = 0;int bytes = wav->data.size;//数据长度if (wav->format.channels == 2 && wav->format.bits_per_sample == 16) {//双声道,16位numOfSample = bytes >> 2;//样本数//读取数据short* buff = (short*)malloc(sizeof(short) * numOfSample * 2);wav_read_data(wav, buff, sizeof(short), numOfSample * 2);//转换为单声道short* newBuff = (short*)malloc(sizeof(short) * numOfSample);memset(newBuff, 0, sizeof(short) * numOfSample);for (int i = 0; i < numOfSample; i++) {newBuff[i] = (buff[2 * i] + buff[2 * i + 1]) / 2;}data = (float*)malloc(numOfSample * sizeof(float));memset(data, 0, sizeof(short)*numOfSample);float scale = 1.0f / ((float)(0x7FFF));for (int i = 0; i < numOfSample; i++) {data[i] = scale * (float)newBuff[i];}//释放临时存储区(buff和newBuff)free(buff);free(newBuff);}else if (wav->format.channels == 1 && wav->format.bits_per_sample == 16) {//单声道,16位numOfSample = bytes >> 1;//样本数量//读取数据short* buff = (short*)malloc(sizeof(short) * numOfSample);wav_read_data(wav, buff, sizeof(short), numOfSample);data = (float*)malloc(numOfSample * sizeof(float));memset(data, 0, sizeof(short) * numOfSample);float scale = 1.0f / ((float)(0x7FFF));for (int i = 0; i < numOfSample; i++) {data[i] = scale * (float)buff[i];}//释放临时内存空间free(buff);}//窗口大小int winLen = 800;int rang = 1600;//高斯窗口double* weight = getWhight(winLen, rang, 0, 1);double * levelShape = (double*)malloc((numOfSample - winLen + 1) * sizeof(double));memset(levelShape, 0, (numOfSample - winLen + 1)*sizeof(double));for (int i = 0; i < numOfSample - winLen + 1; i++) {for (int j = 0; j < winLen; j++) {levelShape[i] += fabs(data[i + j] * weight[j]);//高斯平滑}}//将处理后的数据写入到data.txt文件中FILE *fpWrite = fopen("data.txt", "w");if (fpWrite == NULL){return 0;}for (int i = 0; i < numOfSample - winLen + 1; i++) {fprintf(fpWrite, "%f\r\n", levelShape[i]);printf("%f\r\n", levelShape[i]);}fclose(fpWrite);printf("处理后的数据已经写入到data.txt文件中。");//关闭wave文件wav_close(&wav);//释放存储空间free(data);free(levelShape);getch();return 1;}
}
最终整合成了如下的图形,接着,就可以根据阈值进行声音的截取了。
更多推荐:Windows基础-实时录音程序、Wave头分析代码、基于C语言的 WAV 文件双声道转单声道的实现
【C语言】Wave文件处理相关推荐
- C语言里的out函数,c语言 vc 用waveout函数写wave文件播放器
用WaveOut函数写wave文件播放器 要炒菜的话,就得先准备工具,如锅.铲子.炉灶等.对程序来说,就是各种函数的应用.WaveOut函数在windowsAPI中属于低阶接口,用来播放的话需要用到下 ...
- c语言读文件编译,C语言读取wav文件的问题,请大侠,编译问题。
已结贴√ 问题点数:20 回复次数:5 C语言读取wav文件的问题,请大侠,编译问题. 代码如下:#include #include LRESULT CALLBACK WndProc (HWND, U ...
- 【学习日志】2022.09.02 (C++)strcmp和stricmp、strcmpi三者之间的区别、C语言判断文件后缀名、ZENO Audio Update、TEN MINUTES PHYSICS
(C++)strcmp和stricmp.strcmpi三者之间的区别 (strcmpi在Windows C标准库实现,但不在GNU C标准库实现) #include <string.h> ...
- c++文件读取空格_程序员术与道:术—C语言对文件进行处理,文件处理的基本操作...
各种编程语言都实现了文件的基本操作,提供了对应的接口,本篇文章先为你介绍C语言对文件进行处理和文件处理的基本操作.主要从以下几个方面进行介绍: 读取文件 写入文件 重命名文件 读取目录 读取目录下的文 ...
- linux c 判断文件打开文件,Linux 用C语言判断文件和文件夹
Linux 用C语言判断文件和文件夹 #include #include #include #include int access(const char *pathname, int mode); i ...
- c语言程序头文件作用,C语言头文件
C语言头文件教程 C 语言的头文件一般都是 .h 做为结尾的. C语言头文件详解 语法 #include 参数 参数 描述 filename 我们需要引入的头文件的名称. 说明 C 语言的头文件一般都 ...
- c语言课件 文件,C语言课件--文件.ppt
C语言课件--文件 例12-5 /*将字符串"apple", "grape", "pear" 写入到磁盘文件f12-5.txt中,然后再从该 ...
- 使用python写Wave文件
1.Wave文件 WAV是Microsoft开发的一种声音文件格式,虽然它支持多种压缩格式,不过它通常被用来保存未压缩的声音数据(PCM脉冲编码调制).WAV有三个重要的参数:声道数.取样频率和量 ...
- c语言删除文件中的结构体_C语言插入、删除、更改文件内容
我们平时所见的文件,例如 txt.doc.mp4 等,文件内容是按照从头到尾的顺序依次存储在磁盘上的,就像排起一条长长的队伍,称为顺序文件. 除了顺序文件,还有索引文件.散列文件等,一般用于特殊领域, ...
最新文章
- 和世界冠军一起准备ACM!清华杜瑜皓来了:连续4年ACM中国赛区冠军
- php伪类型,解密PHP伪类型和伪变量的含义
- 数据包分析中Drop和iDrop的区别
- 文献学习(part12)--GMNN: Graph Markov Neural Networks
- java中使用jython_将Jython嵌入到您的Java代码库中
- 如何在 Flink 中规划 RocksDB 内存容量?
- SDL_gfx-2.0.23在windows平台下的编译及例子
- excel : 如何快速跳到某一行
- 使用php下载的文件打不开,自己用着没问题,客户用就不行?
- 棋牌游戏开发运营技巧列举 如何才能提高平台留存率
- openstack neutron网络插件学习(二)【linux-bridge实现】
- 用acts_as_paranoid 做假删除
- Ardunio开发实例-简单声音感应控制开关
- JVM成神之路-Java内存模型(JMM)
- 计算机蓝屏的解决方法,电脑开机蓝屏怎么解决?电脑蓝屏原因及解决方法
- Arduino基础学习-声音信号输出
- 【JavaScript】- 实现图片放大镜效果
- certbot为NGINX配置SSL证书
- 三菱机器人Tool坐标系增量式运动指令
- (转)微信获取到的经纬度坐标不精准的问题
热门文章
- lisp点位提取_晓东CAD家园-论坛-A/VLISP-[LISP函数]:计算到指定点指定距离的点的点位 - Powered by Discuz!...
- 排序之插入排序(C++实现)
- 龙应台写给儿子安德烈的一段话
- Hoops编程指南:04_3_user_interaction_window_system_interaction
- 创业公司股份分配合适方法
- NO.012-2018.02.17《题都城南庄》唐代:崔护
- 材料科学与计算机模拟,3材料科学与行为工艺但的计算机模拟.ppt
- 目标检测中评估每类目标在IOU为0.5的PrecisionRecall
- php投票 每天投票一次,微信投票如何设置投票规则,投票设置可以每天都投吗?...
- Linux 命令之查看系统版本命令