CS50 Week4实验:fwrite与fputc处理WAV采样为何结果不同?
问题
我尝试用两种方式复制WAV音频采样:
【fwrite实现】
int16_t samp; while (fread(&samp, sizeof(int16_t), 1, input)) { samp *= factor; fwrite(&samp, sizeof(int16_t), 1, output); }
【fputc实现】
int16_t samp; while ((samp = fgetc(input)) != EOF) { samp *= factor; fputc(samp, output); }
前者输出音频正常,后者声音失真。已知失真由samp *= factor导致:使用整数因子时音量调整正常,使用浮点因子则失真。
我的问题是:为何fwrite方式可行,而fputc方式不行?两者都是int类型变量与float类型因子相乘,前者为何能正常工作?
附完整参考程序:
// Modifies the volume of an audio file #include <stdint.h> #include <stdio.h> #include <stdlib.h> // Number of bytes in .wav header const int HEADER_SIZE = 44; int main(int argc, char *argv[]) { // Check command-line arguments if (argc != 4) { printf("Usage: ./volume input.wav output.wav factor\n"); return 1; } // Open files and determine scaling factor FILE *input = fopen(argv[1], "r"); if (input == NULL) { printf("Could not open file.\n"); return 1; } FILE *output = fopen(argv[2], "w"); if (output == NULL) { printf("Could not open file.\n"); return 1; } float factor = atof(argv[3]); // Copy header from input file to output file uint8_t *hder = malloc(sizeof(uint8_t) * HEADER_SIZE); fread(hder, sizeof(uint8_t), HEADER_SIZE, input); fwrite(hder, sizeof(uint8_t), HEADER_SIZE, output); free(hder); // Read samples from input file and write updated data to output file int16_t samp; while (fread(&samp, sizeof(int16_t), 1, input)) { samp *= factor; fwrite(&samp, sizeof(int16_t), 1, output); } // while ((samp = fgetc(input)) != EOF) // { // samp *= factor; // fputc(samp, output); // } // Close files fclose(input); fclose(output); }
原因分析
核心差异在于两种方式处理的数据单位完全不同,和int与float相乘本身无关:
fread/fwrite的正确逻辑
fread每次读取2字节(正好是int16_t的大小),直接组装成一个完整的16位音频采样值存入samp。- 执行
samp *= factor时,是对完整的16位采样值进行浮点缩放,计算后的结果仍以完整的16位整数形式写入文件,完全符合WAV音频的采样存储规则,所以输出正常。
fgetc/fputc的错误逻辑
fgetc每次只读取1字节(8位)数据,而WAV的16位采样是由两个连续字节组合而成的(小端或大端格式)。你现在相当于把每个采样拆成单独的字节处理,而非操作完整的采样值。- 执行
samp *= factor时,是对单个8位字节进行缩放,而非完整的16位采样。比如一个16位采样值0x1234(十进制4660),拆成字节后是0x34和0x12,单独缩放后再写入,组装出的数值和原采样缩放后的结果完全不符,必然导致失真。 - 用整数因子时看似正常只是巧合:比如因子为2,每个字节乘2后若未溢出,组装后的16位值恰好等于原采样乘2的结果。但浮点因子会打破这种巧合,彻底破坏采样的数值结构。
额外的EOF隐患
fgetc返回的是int类型,读到EOF时返回EOF(通常为-1),你把它存入int16_t的samp,会把全1的二进制值当成正常采样写入,进一步引入噪声。
总结:fread/fwrite是按完整的16位采样单位处理数据,符合WAV的存储规则;而fgetc/fputc是按单个字节处理,从根本上错误拆解了采样数据,自然会导致失真。
内容的提问来源于stack exchange,提问作者Viktor
相关产品推荐
相关产品推荐

