前言
今天来对自己找到的一份网上的有关将wav文件的采样位数从16位转化成8位的程序进行解析,通过分析后加入到自己的程序中,从而完成一个自己毕设软件中的一个功能。
程序解析及源码
函数学习
首先对程序中遇到的新函数进行学习,便于后面对于程序的理解。
malloc与free函数
由于malloc函数与free函数一般都是连用的,故而放在一起进行说明。
作用:malloc函数用于向操作系统申请一片内存,free函数对应的是释放这片申请的内存,如果申请后不释放会造成内存泄漏的错误,故而free函数是必要的。
头文件: #include <stdlib.h>
函数原型:
void *malloc(size_t size);
void free(void *ptr);
参数:
malloc:size是指需要分配的字节数。
free:ptr-- 指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。
返回值:
malloc:分配成功,则返回一个指向分配空间的指针。失败返回NULL指针。
free:void类型,不返回任何值。
memcpy函数
作用:C和C++使用的内存拷贝函数。从源source所指的内存地址的起始位置开始拷贝n个字节到目标destin所指的内存地址的起始位置中。
头文件: #include <string.h>
函数原型:
void *memcpy(void destin, void source, unsigned n);
参数:
destin: 指向用于存储复制内容的目标数组,类型强制转换为 void 指针。
source:指向要复制的数据源,类型强制转换为 void 指针。
n: 要被复制的字节数。
返回值:
该函数返回一个指向目标存储区destin的指针。
feof函数
作用:检测流上的文件结束符。文件结束:返回非0值,文件未结束,返回0值。
头文件: #include <stdio.h>
函数原型:
int feof(FILE *stream);
参数:
stream:FILE结构的指针
返回值:
文件结束:返回非0值,文件未结束,返回0值。
PS:feof判断文件结束是通过读取函数fread/fscanf等返回错误来识别的,故而判断文件是否结束应该是在读取函数之后进行判断。比如,在while循环读取一个文件时,如果是在读取函数之前进行判断,则如果文件最后一行是空白行,可能会造成内存错误。
源码解析
下面附上我注释过的源码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* wav音频头部格式 */
typedef struct _wave_pcm_hdr
{
char riff[4]; // = "RIFF"
int size_8; // = FileSize - 8
char wave[4]; // = "WAVE"
char fmt[4]; // = "fmt "
int fmt_size; // = 下一个结构体的大小 : 16
short format_tag; // = PCM : 1
short channels; // = 通道数 : 1
int samples_per_sec; // = 采样率 : 8000 | 6000 | 11025 | 16000
int avg_bytes_per_sec; // = 每秒字节数 : samples_per_sec * bits_per_sample / 8
short block_align; // = 每采样点字节数 : wBitsPerSample / 8
short bits_per_sample; // = 量化比特数: 8 | 16
char data[4]; // = "data";
int data_size; // = 纯数据长度 : FileSize - 44
} wave_pcm_hdr;
int wave_16bit_to_8bit(char *source_file,char *dest_file)
{
short *data_16 = NULL ; //short型的指针
char data_8 = 0 ; //8位的数据
unsigned char mdata_8 = 0 ; //8位写入数据
unsigned char* dstemp = (unsigned char*)malloc(4); //向操作系统申请4字节的内存空间,返回一个unsigned char的指针
FILE *pInput = NULL , * pOutput = NULL ; //操作文件的指针
wave_pcm_hdr wave_header_16 = {0}; //定义并初始化16位采样的wav文件的文件头
wave_pcm_hdr wave_header_8 = {0}; //同上,8位
//打开文件
if( strcmp(source_file,dest_file) == 0 )
{
printf("源文件名与目标文件名同名了;\n");
return -2 ;
}
pInput = fopen(source_file,"rb");
pOutput = fopen(dest_file,"wb");
if(pInput == NULL || pOutput == NULL)
{
printf("打开源文件或打开目标文件失败;\n");
return -1 ;
}
fread(&wave_header_16,1,44,pInput); //读取原本16位采样的wav文件头
printf("bits_per_sample:%d;\n",wave_header_16.bits_per_sample);//采样位数16
memcpy(&wave_header_8,&wave_header_16,sizeof(wave_pcm_hdr)); //将16位的wav文件头数据拷贝到8位里面去
wave_header_8.size_8 = (wave_header_16.size_8+8)/2 + 14; //根本不用变。去掉此行也可以
wave_header_8.avg_bytes_per_sec = 8000; //每秒字节数8000
wave_header_8.block_align = 1 ; //采样帧大小1字节
wave_header_8.bits_per_sample = 8 ;//改为8 ,上面的值也需要随之改变
wave_header_8.data_size = wave_header_16.data_size/2 ; //data区数据变为16位的一半
fwrite(&wave_header_8,1,sizeof(wave_pcm_hdr),pOutput); //将文件头写入目标文件
while(!feof(pInput)) //循环检测是否到了文件尾
{
fread(dstemp,1,4,pInput); // 每次从源文件读取4字节data数据到dstemp,前面fread已经读取了44字节头部,因此直接到了data区
//每次将两字节数据转化为1字节数据存到输出文件中
data_16 = (short*)dstemp; //将dstemp强转为short指针再
data_8 = (*data_16) >> 8; //将2字节数据右移8位,就是清除1字节数据,让原本16位单声道的高8位清除,再赋值给8位的data
mdata_8 = data_8 + 128 ; //要将data_8+128才赋值给mdata_8是???,应该不加也可以
printf("%d\n",mdata_8);
fwrite(&mdata_8,1,1,pOutput);
data_16 = (short*)(dstemp + 2);
data_8 = (*data_16) >> 8;
mdata_8 = data_8 + 128 ;
fwrite(&mdata_8,1,1,pOutput);
}
fclose(pOutput);
fclose(pInput);
return 0 ;
}
int main(int argc , char **argv)
{
wave_16bit_to_8bit("tts_sample.wav","tts_sample.wav");
return 0 ;
}
简单的说,上面的代码简单的通过将16位wav文件采样数据右移8位,清除了高8位的数据,再将低8位数据赋值给了8位wav文件的采样数据,完成了wav文件16位到8位的变化。其中源码作者有将每个得到的8位data数据加了128,我觉得不加也可以,这要在后面的测试中发现问题了。今天的文章先写到这里,下篇文章,尝试将此功能添加到我的毕设软件中。