如何用C语言编写可被识别的WAV文件?字节序问题排查
问题描述
我尝试用C语言编写WAV文件,但生成的文件无法被Audacity或VLC识别。我了解WAV部分头数据采用大端字节序,但不确定在C语言中写入二进制文件时如何处理,推测这可能是问题根源。当前代码无报错,但生成的WAV文件不被识别。WAV参数:16bit、96kHz、双声道。代码如下:
// Writes data to wav file FILE *fp = fopen("output/video_signal.wav", "w"); if (fp == NULL) { printf("Output file couldn't be opened!\n"); return 4; } int16_t byte; uint32_t length = 160+(4800*frame_count); float multiplier = 32767 / sync_level; // scales signal to use full 16bit resolution //// WAVE Header Data unsigned char riff[] = {'R', 'I', 'F', 'F'}; fwrite(&riff, 1, 4, fp); uint32_t file_size = length + 44; fwrite(&file_size, 4, 1, fp); // Overall file size unsigned char wave[] = {'W', 'A', 'V', 'E'}; fwrite(&wave, 1, 4, fp); unsigned char fmt[] = {'f', 'm', 't'}; // ? fwrite(&fmt, 1, 4, fp); uint32_t fmt_length = 16; // ? fwrite(&fmt_length, 4, 1, fp); uint16_t fmt_type = 1; // 1 = PCM fwrite(&fmt_type, 2, 1, fp); uint16_t chan_num = 2; // Number of channels fwrite(&chan_num, 2, 1, fp); uint32_t rate = 96000; // Sample rate fwrite(&rate, 4, 1, fp); uint32_t funny_number = rate * 16 * chan_num; // (Sample Rate * BitsPerSample * Channels) / 8 fwrite(&funny_number, 4, 1, fp); uint16_t another_format = 4; // 4 = 16bit stereo fwrite(&another_format, 2, 1, fp); uint16_t bits = 16; // Bit depth / Bits per sample fwrite(&bits, 2, 1, fp); unsigned char data_start[] = {'d', 'a', 't', 'a'}; // Marks the start of the data fwrite(&data_start, 1, 4, fp); fwrite(&length, 4, 1, fp); // Data size for (int i = 0; i < length; i++) { byte = (channel1[i] * multiplier); fwrite(&byte, 2, 1, fp); byte = (channel2[i] * multiplier); fwrite(&byte, 2, 1, fp); } fclose(fp);
问题排查与修复方案
你的代码里存在多个导致WAV文件无法被识别的问题,以下是逐一修复的要点:
1. 文件打开模式错误
用"w"模式打开文件是文本模式,写入二进制数据时会自动转换特殊字符(比如Windows下的换行符),破坏WAV的二进制结构。必须改用二进制写入模式:
FILE *fp = fopen("output/video_signal.wav", "wb");
2. 字节序处理错误
WAV文件的所有多字节数值(如文件大小、采样率等)要求是小端字节序,不是大端。当前代码直接写入主机字节序,若你的机器是大端架构,头数据会完全错误。需要用字节序转换函数处理:
- 16位整数:用
htole16()或__builtin_bswap16()(根据编译器调整) - 32位整数:用
htole32()或__builtin_bswap32()
示例:
uint32_t file_size = htole32(length + 44); fwrite(&file_size, 4, 1, fp);
3. "fmt"块标识不完整
WAV的格式块标识是{'f','m','t',' '}(最后有一个空格),你定义的数组少了这个空格,导致播放器无法识别格式块:
unsigned char fmt[] = {'f', 'm', 't', ' '}; fwrite(fmt, 1, 4, fp);
4. 字节速率计算错误
你注释里的公式是(Sample Rate * BitsPerSample * Channels) / 8,但代码里漏掉了除以8的步骤,正确计算应为:
uint32_t byte_rate = htole32((rate * bits * chan_num) / 8); fwrite(&byte_rate, 4, 1, fp);
5. 数据长度与实际写入不匹配
你定义的length是样本数,但WAV的data块长度需要是字节数。双声道16位情况下,每个样本对应4字节(2通道×2字节),所以实际数据字节数应为length * 4,同时文件总大小的计算也要对应调整:
uint32_t sample_count = 160 + (4800 * frame_count); uint32_t data_bytes = sample_count * 2 * sizeof(int16_t); uint32_t file_size = htole32(data_bytes + 44);
循环部分也要遍历样本数,而非字节数:
for (int i = 0; i < sample_count; i++) { int16_t sample1 = htole16((int16_t)(channel1[i] * multiplier)); fwrite(&sample1, 2, 1, fp); int16_t sample2 = htole16((int16_t)(channel2[i] * multiplier)); fwrite(&sample2, 2, 1, fp); }
修复后的关键代码片段
FILE *fp = fopen("output/video_signal.wav", "wb"); if (fp == NULL) { printf("Output file couldn't be opened!\n"); return 4; } uint32_t sample_count = 160 + (4800 * frame_count); float multiplier = 32767.0f / sync_level; uint32_t data_bytes = sample_count * 2 * sizeof(int16_t); uint32_t file_size = htole32(data_bytes + 44); // RIFF chunk unsigned char riff[] = {'R','I','F','F'}; fwrite(riff, 1, 4, fp); fwrite(&file_size, 4, 1, fp); // WAVE identifier unsigned char wave[] = {'W','A','V','E'}; fwrite(wave, 1, 4, fp); // fmt chunk unsigned char fmt[] = {'f','m','t',' '}; fwrite(fmt, 1, 4, fp); uint32_t fmt_len = htole32(16); fwrite(&fmt_len, 4, 1, fp); uint16_t fmt_type = htole16(1); // PCM fwrite(&fmt_type, 2, 1, fp); uint16_t chan_num = htole16(2); fwrite(&chan_num, 2, 1, fp); uint32_t rate = htole32(96000); fwrite(&rate, 4, 1, fp); uint16_t bits = htole16(16); uint32_t byte_rate = htole32((96000 * 16 * 2) / 8); fwrite(&byte_rate, 4, 1, fp); uint16_t block_align = htole16((16 * 2) / 8); fwrite(&block_align, 2, 1, fp); fwrite(&bits, 2, 1, fp); // data chunk unsigned char data_start[] = {'d','a','t','a'}; fwrite(data_start, 1, 4, fp); uint32_t data_len = htole32(data_bytes); fwrite(&data_len, 4, 1, fp); // Write samples for (int i = 0; i < sample_count; i++) { int16_t s1 = htole16((int16_t)(channel1[i] * multiplier)); int16_t s2 = htole16((int16_t)(channel2[i] * multiplier)); fwrite(&s1, 2, 1, fp); fwrite(&s2, 2, 1, fp); } fclose(fp);
内容的提问来源于stack exchange,提问作者Jake A
相关产品推荐
相关产品推荐

