使用C处理音频wav文件

我正在处理wav文件的幅度并按一些小数因子缩放它。 我试图以有效记忆的方式阅读和重写文件,同时也试图解决语言的细微差别(我是C的新手)。 该文件可以是8位或16位格式。 我想这样做的方法是首先将标题数据读入一些预定义的结构,然后在循环中处理实际数据,我将把一大块数据读入缓冲区,做任何需要的事情,然后将其写入输出。

#include  #include  typedef struct header { char chunk_id[4]; int chunk_size; char format[4]; char subchunk1_id[4]; int subchunk1_size; short int audio_format; short int num_channels; int sample_rate; int byte_rate; short int block_align; short int bits_per_sample; short int extra_param_size; char subchunk2_id[4]; int subchunk2_size; } header; typedef struct header* header_p; void scale_wav_file(char * input, float factor, int is_8bit) { FILE * infile = fopen(input, "rb"); FILE * outfile = fopen("outfile.wav", "wb"); int BUFSIZE = 4000, i, MAX_8BIT_AMP = 255, MAX_16BIT_AMP = 32678; // used for processing 8-bit file unsigned char inbuff8[BUFSIZE], outbuff8[BUFSIZE]; // used for processing 16-bit file short int inbuff16[BUFSIZE], outbuff16[BUFSIZE]; // header_p points to a header struct that contains the file's metadata fields header_p meta = (header_p)malloc(sizeof(header)); if (infile) { // read and write header data fread(meta, 1, sizeof(header), infile); fwrite(meta, 1, sizeof(meta), outfile); while (!feof(infile)) { if (is_8bit) { fread(inbuff8, 1, BUFSIZE, infile); } else { fread(inbuff16, 1, BUFSIZE, infile); } // scale amplitude for 8/16 bits for (i=0; i  MAX_8BIT_AMP) { outbuff8[i] = MAX_8BIT_AMP; } } else { outbuff16[i] = factor * inbuff16[i]; if ((int)outbuff16[i] > MAX_16BIT_AMP) { outbuff16[i] = MAX_16BIT_AMP; } else if ((int)outbuff16[i] < -MAX_16BIT_AMP) { outbuff16[i] = -MAX_16BIT_AMP; } } } // write to output file for 8/16 bit if (is_8bit) { fwrite(outbuff8, 1, BUFSIZE, outfile); } else { fwrite(outbuff16, 1, BUFSIZE, outfile); } } } // cleanup if (infile) { fclose(infile); } if (outfile) { fclose(outfile); } if (meta) { free(meta); } } int main (int argc, char const *argv[]) { char infile[] = "file.wav"; float factor = 0.5; scale_wav_file(infile, factor, 0); return 0; } 

我在最后得到不同的文件大小(对于40Mb文件大约1k左右),我怀疑这是因为我正在向输出写入整个缓冲区,即使文件可能已终止在填充整个缓冲区大小之前。 此外,输出文件搞砸了 – 不会播放或打开 – 所以我可能做错了。 关于我搞砸的地方的任何提示都会很棒。 谢谢!

1您正在读取其他分支中的字节而不是16位样本:

 while (!feof(infile)) { if (is_8bit) { fread(inbuff8, 1, BUFSIZE, infile); } else { fread(inbuff16, 1, BUFSIZE, infile); // <-- should be BUFSIZE*2 } 

2缩放时不会使值饱和,例如原始16位采样= 32000且因子= 1.5将环绕整数值而不是将其钳位到最大值32767。

3你根本不看RIFF和其他标题。 在WAV文件中,音频数据可能后跟一些信息页脚或前面有其他标题。 或者换句话说:您的header结构太静态了。 您还应该从文件中读取WAV格式,而不是让参数说明它是8位样本。

4这不会发生:

  outbuff16[i] = factor * inbuff16[i]; if ((int)outbuff16[i] > MAX_16BIT_AMP) 

8位/ 16位值永远不会大于255/32768,除非您的计算机在整数溢出时将一些魔术位插入内存中:P

音频样本已签名,因此范围为-128; 127和-32768; 32767。 溢出检查必须在乘法表达式中进行。 您还可以对浮点到整数舍入模式进行假设,这种模式是可配置的,应予以考虑。 可能是if(roundf(factor * inbuff16[i]) > 32767 || roundf(factor * inbuff16[i]) < -32768)

5您不存储fread的结果,因此您将向输出文件中写入太多样本。

6作为最后一点,你正在重新发明轮子。 只要这是学习,那没关系。 否则,您应该使用现有的库。

使用库来读取和写入声音文件要好得多。 例如libsndfile 。 该网页上有一个“其他类似项目”列表,您也可以查看。 sndfile-tools可以是学习如何使用库的好代码示例。

我建议在hex编辑器中查看原始文件和输出文件,看看你是否正在重写数据。 如果生成的文件无法播放或打开,则输出文件的标题可能不正确。

另一种选择是删除音频处理逻辑,只需将源文件读入内部缓冲区并将其写入文件即可。 如果您的代码可以以这种方式生成有效的工作输出文件,那么您可以将问题范围缩小到处理代码。

您可能还希望从小于40Mb的文件开始。 如果不出意外,请复制该输入文件并将其修剪为几秒钟的音频。 较小的文件将更容易手动检查。

编辑:fread()fwrite()的调用需要validation其返回值。 这些函数返回读取或写入的元素数,如果对任一函数的调用返回的值小于预期值,那么这可能是文件大小差异的来源。

此外, fread的第二个参数是以字节为单位。 因此,如果你想读取整个缓冲区,你需要说一些更像fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile); 。 当前代码只能读取BUFSIZE字节(适用于8位情况,但为了清晰起见,我建议更改它)。

读取WAV标题也不需要以下行(使标题长48个字节,而不是“标准”44):

 short int extra_param_size; 

如果可能,您可能希望查看与C不同的语言,除非它专门用于C应用程序。

  • 例如,python有一个很好的wav包,可以轻松读取和写入wav文件。
  • 对于更专业或学术用途,首先是MATLAB,它也可以非常容易地读取wav文件(直接转换为向量,然后作为单个表达式进行操作)。