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

生成DTMF拨号音WAV文件时出现损坏无声音问题求助

DTMF WAV文件损坏无声音问题修复

核心错误分析

  • WAV头字段赋值错误:代码中多处使用==比较运算符而非=赋值运算符,导致SubChunk2Size、ChunkSize、ByteRate等关键头字段未正确初始化,这是文件损坏的主要原因。
  • tone_length类型错误:将tone_length定义为int,但atof(argv[2])返回浮点数(如0.5会被截断为0),导致采样数计算完全错误。
  • 电话号码校验逻辑失效:用atof将电话号码字符串转为整数,无法处理*、#、-等字符,且判断条件逻辑混乱,根本无法校验输入合法性。
  • 采样数据覆盖问题:内层循环每次从pData[0]开始写入,覆盖之前数字的采样数据,最终只保留最后一个数字的采样。
  • WAV头缺失必要字段:未设置BlockAlign字段,这是WAV格式要求的必填项。
  • 内存泄漏与冗余操作:pFile = malloc(sizeof(WAVEFILE));完全多余,后续被fopen覆盖,导致内存泄漏。
  • 头字段计算错误:SubChunk2Size和ChunkSize的计算公式错误,导致文件大小标识不正确。
  • 三角函数精度不足:使用硬编码的3.14159而非标准库的M_PI,精度不足;且未使用浮点类型计算相位,可能导致溢出或精度损失。

修复后的代码

#define _USE_MATH_DEFINES
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>

#define BYTES_PER_SAMPLE 2
#define SAMPLE_RATE 44100
#define NUM_CHANNELS 1
#define AMPLITUDE 16382

void show_usage(void);

typedef struct 
{
     uint8_t ChunkID[4];  
     uint32_t ChunkSize;      // 4 + (8+SubChunk1Size) + (8+SubChunk2Size)
     uint8_t Format[4];

     uint8_t SubChunk1ID[4];
     uint32_t SubChunk1Size;

     uint16_t AudioFormat;
     uint16_t NumChannels;

     uint32_t SampleRate;
     uint32_t ByteRate;       //SampleRate * NumChannels * BitsPerSample/8
     uint16_t BlockAlign;     //NumChannels * BitsPerSample/8
     uint16_t BitsPerSample;

     uint8_t SubChunk2ID[4];
     uint32_t SubChunk2Size;  //NumSamples * NumChannels * BitsPerSample/8
} WAVEFILE;  

int main (int argc, const char* argv[])
{
    FILE *pFile = NULL;
    int16_t *pData;
    WAVEFILE myWavHeader;
    WAVEFILE *pWave = &myWavHeader;
    int phoneLength = strlen(argv[3]);
    int freq1, freq2;
    float tone_length;

    // 检查参数数量
    if (argc != 4)
    {
        show_usage();        
        return -1;
    }

    // 校验电话号码每个字符的合法性
    for (int i = 0; i < phoneLength; i++)
    {
        char c = argv[3][i];
        if (!( (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '-' ))
        {
            show_usage();       
            return -1; 
        }
    }

    // 转换并校验tone_length
    tone_length = atof(argv[2]);
    if (!(tone_length >= 0.1 && tone_length <= 1.0))
    {
        show_usage();       
        return -1;
    }

    // 计算总采样字节数并分配内存
    uint32_t total_samples = phoneLength * (uint32_t)(SAMPLE_RATE * tone_length);
    uint32_t total_bytes = total_samples * BYTES_PER_SAMPLE;
    pData = malloc(total_bytes);
    if(pData == NULL)
    {
        puts("内存分配失败:无法分配采样数据内存");
        return -1;
    }

    // 填充WAV头字段
    memcpy(myWavHeader.ChunkID, "RIFF", 4);
    memcpy(myWavHeader.Format, "WAVE", 4);
    memcpy(myWavHeader.SubChunk1ID, "fmt ", 4);
    memcpy(myWavHeader.SubChunk2ID, "data", 4);

    myWavHeader.SubChunk1Size = 16;
    myWavHeader.AudioFormat = 1; // PCM
    myWavHeader.NumChannels = NUM_CHANNELS;
    myWavHeader.SampleRate = SAMPLE_RATE;
    myWavHeader.BitsPerSample = 16;
    myWavHeader.BlockAlign = myWavHeader.NumChannels * (myWavHeader.BitsPerSample / 8);
    myWavHeader.ByteRate = myWavHeader.SampleRate * myWavHeader.BlockAlign;
    myWavHeader.SubChunk2Size = total_bytes;
    myWavHeader.ChunkSize = 36 + myWavHeader.SubChunk2Size; // RIFF块大小计算

    // 打开文件
    pFile = fopen(argv[1], "wb");
    if( pFile == NULL)
    {
        puts("文件打开失败:无法创建或写入目标文件");
        free(pData);
        return -1;
    }

    // 生成DTMF采样数据
    int sample_idx = 0;
    int samples_per_tone = (int)(SAMPLE_RATE * tone_length);
    for(int i = 0; i < phoneLength; i++)
    {
        char c = argv[3][i];
        // 设置对应频率,'-'为静音(频率0)
        switch(c)
        {
            case '1': freq1=1209; freq2=697; break;
            case '2': freq1=1336; freq2=697; break;
            case '3': freq1=1477; freq2=697; break;
            case '4': freq1=1209; freq2=770; break;
            case '5': freq1=1336; freq2=770; break;
            case '6': freq1=1477; freq2=770; break;
            case '7': freq1=1209; freq2=852; break;
            case '8': freq1=1336; freq2=852; break;
            case '9': freq1=1477; freq2=852; break;
            case '*': freq1=1209; freq2=941; break;
            case '0': freq1=1336; freq2=941; break;
            case '#': freq1=1477; freq2=941; break;
            case '-': freq1=0; freq2=0; break;
            default: freq1=0; freq2=0; break;
        }

        // 生成当前数字的采样
        for(int j = 0; j < samples_per_tone; j++)
        {
            double phase1 = 2 * M_PI * freq1 * j / SAMPLE_RATE;
            double phase2 = 2 * M_PI * freq2 * j / SAMPLE_RATE;
            int16_t sample = AMPLITUDE * (sin(phase1) + sin(phase2));
            pData[sample_idx++] = sample;
        }
    }

    // 写入WAV头
    if(fwrite(pWave, sizeof(WAVEFILE), 1, pFile) != 1)
    {
        puts("写入文件头失败");
        fclose(pFile);
        free(pData);
        return -1;
    }

    // 写入采样数据
    if(fwrite(pData, total_bytes, 1, pFile) != 1)
    {
        puts("写入采样数据失败");
        fclose(pFile);
        free(pData);
        return -1;
    }

    // 清理资源
    fclose(pFile);
    free(pData);
    puts("WAV文件生成成功");
    return 0;
}

void show_usage(void)
{
   printf("\n用法: ./a.exe X Y Z\n\n");
   printf("  X 是要创建的WAV文件名\n");
   printf("  Y 是每个DTMF音调的持续时间(秒),有效值范围0.1-1.0\n");
   printf("  Z 是要生成音调的数字字符串,合法字符为0-9、#、*和-(静音)\n");
   printf("\n示例: ./a.exe wavy.wav 0.3 123-3831\n");
}

额外说明

  • 修复了所有头字段的赋值和计算错误,确保WAV格式符合标准。
  • 改用float存储tone_length,避免浮点数截断问题。
  • 逐个字符校验电话号码输入,确保合法性。
  • 调整采样数据写入逻辑,每个数字的采样写入到正确的偏移位置,不再覆盖。
  • 添加了M_PI的定义,提升三角函数计算精度。
  • 完善了错误处理流程,内存分配或文件操作失败时及时释放资源并退出。
  • 支持-字符作为静音,生成全0采样。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 16:17:00