You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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相乘本身无关:

  1. fread/fwrite的正确逻辑

    • fread每次读取2字节(正好是int16_t的大小),直接组装成一个完整的16位音频采样值存入samp。
    • 执行samp *= factor时,是对完整的16位采样值进行浮点缩放,计算后的结果仍以完整的16位整数形式写入文件,完全符合WAV音频的采样存储规则,所以输出正常。
  2. fgetc/fputc的错误逻辑

    • fgetc每次只读取1字节(8位)数据,而WAV的16位采样是由两个连续字节组合而成的(小端或大端格式)。你现在相当于把每个采样拆成单独的字节处理,而非操作完整的采样值。
    • 执行samp *= factor时,是对单个8位字节进行缩放,而非完整的16位采样。比如一个16位采样值0x1234(十进制4660),拆成字节后是0x34和0x12,单独缩放后再写入,组装出的数值和原采样缩放后的结果完全不符,必然导致失真。
    • 用整数因子时看似正常只是巧合:比如因子为2,每个字节乘2后若未溢出,组装后的16位值恰好等于原采样乘2的结果。但浮点因子会打破这种巧合,彻底破坏采样的数值结构。
  3. 额外的EOF隐患

    • fgetc返回的是int类型,读到EOF时返回EOF(通常为-1),你把它存入int16_t的samp,会把全1的二进制值当成正常采样写入,进一步引入噪声。

总结:fread/fwrite是按完整的16位采样单位处理数据,符合WAV的存储规则;而fgetc/fputc是按单个字节处理,从根本上错误拆解了采样数据,自然会导致失真。

内容的提问来源于stack exchange,提问作者Viktor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 07:10:29