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

如何用C语言将两个数组写入16位96kHz双声道WAV文件?

实现方案:将双声道int数组写入16位96kHz WAV文件

核心思路

要高效生成符合要求的WAV文件,需解决三个关键问题:正确构造WAV文件头、将int采样转换为16位PCM格式、批量交错写入双声道数据以减少IO开销。

1. WAV文件头结构定义

WAV采用RIFF容器格式,PCM编码的标准头大小为44字节。为避免编译器字节对齐导致的结构大小错误,需使用编译指令强制按1字节对齐:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// 强制1字节对齐,保证结构体大小为44字节
#pragma pack(push, 1)
typedef struct {
    // RIFF块
    char riff_id[4];         // 固定为"RIFF"
    uint32_t riff_size;      // 文件总大小 - 8
    char wave_id[4];         // 固定为"WAVE"
    
    // fmt子块
    char fmt_id[4];          // 固定为"fmt "
    uint32_t fmt_size;       // fmt块大小,PCM为16
    uint16_t audio_format;   // 编码格式,PCM为1
    uint16_t num_channels;   // 声道数,双声道为2
    uint32_t sample_rate;    // 采样率,96000
    uint32_t byte_rate;      // 字节率 = 采样率 * 声道数 * 采样位深/8
    uint16_t block_align;    // 块对齐 = 声道数 * 采样位深/8
    uint16_t bits_per_sample;// 采样位深,16
    
    // data子块
    char data_id[4];         // 固定为"data"
    uint32_t data_size;      // 数据总字节数 = 采样数 * 声道数 * 采样位深/8
} WavHeader;
#pragma pack(pop)

2. 初始化文件头

根据需求填充头字段,注意多字节字段需采用小端字节序(WAV标准要求),跨平台环境下可使用htole16/htole32转换(Linux/macOS)或手动转换:

void init_wav_header(WavHeader* header, uint32_t sample_count) {
    memcpy(header->riff_id, "RIFF", 4);
    memcpy(header->wave_id, "WAVE", 4);
    memcpy(header->fmt_id, "fmt ", 4);
    memcpy(header->data_id, "data", 4);
    
    header->fmt_size = 16;
    header->audio_format = 1; // PCM
    header->num_channels = 2;
    header->sample_rate = 96000;
    header->bits_per_sample = 16;
    
    header->block_align = header->num_channels * (header->bits_per_sample / 8);
    header->byte_rate = header->sample_rate * header->block_align;
    header->data_size = sample_count * header->block_align;
    header->riff_size = 36 + header->data_size; // 36 = 44 - 8(RIFF块的id和size占8字节)
    
    // 若运行在大端系统,需将多字节字段转为小端
    // 示例:header->sample_rate = htole32(header->sample_rate);
}

3. 数据转换与交错

你的channel1和channel2是int类型(通常为32位),需转换为16位有符号整数(int16_t),同时要避免数据溢出。转换后需将左右声道采样交错排列(左、右、左、右...),这是WAV双声道的存储格式:

// 将int数组转换为交错的16位PCM数据
int16_t* convert_and_interleave(int* ch1, int* ch2, uint32_t sample_count) {
    int16_t* pcm_data = malloc(sample_count * 2 * sizeof(int16_t));
    if (!pcm_data) return NULL;
    
    for (uint32_t i = 0; i < sample_count; i++) {
        // 将int缩放到int16_t范围(-32768 ~ 32767),根据实际采样范围调整缩放逻辑
        int32_t sample1 = ch1[i];
        int32_t sample2 = ch2[i];
        
        // 防止溢出,截断到int16_t范围
        sample1 = sample1 > 32767 ? 32767 : (sample1 < -32768 ? -32768 : sample1);
        sample2 = sample2 > 32767 ? 32767 : (sample2 < -32768 ? -32768 : sample2);
        
        pcm_data[2*i] = (int16_t)sample1;
        pcm_data[2*i + 1] = (int16_t)sample2;
    }
    return pcm_data;
}

4. 高效写入文件

最优写入方式是先写入文件头,再一次性写入所有交错后的PCM数据,减少IO系统调用次数(单次大写入比多次小写入效率高)。同时要做好错误检查:

int write_wav_file(const char* filename, int* ch1, int* ch2, uint32_t sample_count) {
    FILE* fp = fopen(filename, "wb");
    if (!fp) {
        perror("Failed to open file");
        return -1;
    }
    
    WavHeader header;
    init_wav_header(&header, sample_count);
    
    // 写入文件头
    if (fwrite(&header, sizeof(WavHeader), 1, fp) != 1) {
        perror("Failed to write header");
        fclose(fp);
        return -1;
    }
    
    // 转换并交错数据
    int16_t* pcm_data = convert_and_interleave(ch1, ch2, sample_count);
    if (!pcm_data) {
        perror("Failed to allocate PCM data");
        fclose(fp);
        return -1;
    }
    
    // 一次性写入所有数据
    size_t written = fwrite(pcm_data, sizeof(int16_t), sample_count * 2, fp);
    if (written != sample_count * 2) {
        perror("Failed to write PCM data");
        free(pcm_data);
        fclose(fp);
        return -1;
    }
    
    free(pcm_data);
    fclose(fp);
    return 0;
}

5. 使用示例

int main() {
    uint32_t sample_count = 4800;
    int* channel1 = malloc(sizeof(int)*sample_count);
    int* channel2 = malloc(sizeof(int)*sample_count);
    
    // 假设这里已经填充了channel1和channel2的数据
    
    if (write_wav_file("output.wav", channel1, channel2, sample_count) == 0) {
        printf("WAV file generated successfully\n");
    }
    
    free(channel1);
    free(channel2);
    return 0;
}

关键优化点

  • 减少IO操作:一次性写入所有PCM数据,避免多次调用fwrite带来的系统开销
  • 内存高效:交错数据时直接分配所需内存,避免临时缓冲区浪费
  • 跨平台兼容:处理字节对齐和字节序问题,保证在不同架构下生成的WAV文件可正常播放
  • 数据安全:添加溢出保护,避免采样转换时出现非法值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:28:12