C/C++实现读写WAV音频文件

974 阅读4分钟

C/C++实现读写WAV音频文件

WAV音频文件本质上是一个二进制文件,C语言可通过fopen读写二进制文件的方法读写WAV音频文件。但WAV文件内不仅存储了音频的数据,还存储的文件信息,因此若要获取正确的WAV文件内容,需要知道WAV存储的的格式,按照WAV格式进行读写操作。

WAV格式文件

WAV(Waveform Audio File Format)是一种常见的音频文件格式,它通常用于存储未经压缩的音频数据。WAV文件遵循RIFF规则,其内容以区块为最小单位进行存储。WAV文件一般由3个区块组成:RIFF区块、Format区块和Data区块。

RIFF区块

名称偏移地址字节数数据类型端序内容
chunkID0x004char大端“RIFF”
chunkSize0x044uint32_t小端文件长度
format0x084char大端“WAVE”

Format区块

名称偏移地址字节数数据类型端序内容
subchunk1ID0x0C4char大端“fmt”,fmt标识
audioFormat0x104uint32_t小端子块大小,通常为16
audioFormat0x142uint16_t小端音频格式代码,1表示PCM无损编码
numChannels0x162uint16_t小端声道数,通常为1(单声道)或2(立体声)
sampleRate0x182uint16_t小端采样率,表示每秒的采样数
byteRate0x1C4uint32_t小端每秒的字节数,等于sampleRate * numChannels * (bitsPerSample / 8)
blockAlign0x202uint16_t小端块对齐,等于numChannels * (bitsPerSample / 8)
bitsPerSample0x222uint16_t小端每个样本的位深度,通常为8、16、24或32

Data区块

名称偏移地址字节数数据类型端序内容
subchunk2ID0x244char大端子块标识,通常为"data"
subchunk2Size0x284uint32_t小端音频数据大小,等于采样数 * numChannels * (bitsPerSample / 8)
data0x2C数据

uint16_tuint32_t 是无符号整数类型的数据类型,它们是C/C++ <stdint.h>标准库中定义的确切位数的整数类型.uint16_t:这是一个16位无符号整数类型,确保有16位位数,没有符号位。uint32_t:这是一个32位无符号整数类型,确保有32位位数,没有符号位。

C语言实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>// 定义WAV文件头部结构
struct WAVHeader {
    char chunkID[4];        // 文件标识,通常为"RIFF"
    uint32_t chunkSize;     // 文件大小
    char format[4];         // 文件格式,“WAVE”
    char subchunk1ID[4];    // 子块标识,“fmt”
    uint32_t subchunk1Size; // 子块大小
    uint16_t audioFormat;   // 音频格式,1为PCM格式
    uint16_t numChannels;   // 声道数,1:单声道,2:双声道
    uint32_t sampleRate;    // 采样率
    uint32_t byteRate;      // 每秒的字节数
    uint16_t blockAlign;    // 块对齐
    uint16_t bitsPerSample; // 采样深度
    char subchunk2ID[4];    // 子块标识,“data”
    uint32_t subchunk2Size; // 子块大小
};
​
// 音频数据结构
struct WAVData {
    struct WAVHeader header;  // 音频头部数据
    uint8_t* sample;          // 音频数据
};
​
// 读取wav格式文件
struct WAVData* audioread(const char* filename);
// 写入wav格式文件
void audiowrite(const char* filename, struct WAVData* audio_data);
​
int main() {
    char filename[30] = "../inputs/test.wav";
    struct WAVData* audio_data = audioread(filename);
    if (audio_data == NULL) {
        perror("文件打开失败!");
        return 1;
    }
​
    char outfilename[30] = "../outputs/test.wav";
    audiowrite(outfilename, audio_data);
​
    return 0;
}
​
struct WAVData* audioread(const char* filename) {
    // 打开文件
    FILE* inputFile = fopen(filename, "rb");
    struct WAVData* audio_data = NULL;
    if (inputFile == NULL) {
        perror("文件代开失败!");
        return NULL;
    }
​
    // 读取文件头部信息
    struct WAVHeader header;
    fread(&header, sizeof(struct WAVHeader), 1, inputFile);
​
    // 验证文件格式
    if (strncmp(header.chunkID, "RIFF", 4) != 0 || strncmp(header.format, "WAVE", 4) != 0) {
        perror("无效文件!");
        fclose(inputFile);
        return NULL;
    }
​
    // 输出wav文件信息
    printf("音频格式: %hu\n", header.audioFormat);
    printf("声道数: %hu\n", header.numChannels);
    printf("采样率: %hu\n", header.sampleRate);
    printf("采样深度: %hu\n", header.bitsPerSample);
​
    // 读取wav文件数据
    audio_data = (struct WAVData*)malloc(sizeof(struct WAVData));
    if (audio_data == NULL) {
        perror("内存申请失败!");
        return NULL;
    }
    audio_data->header = header;
    audio_data->sample = (uint8_t*)malloc(header.subchunk2Size);
    if (audio_data->sample == NULL) {
        perror("内存申请失败!");
        free(audio_data);
        return NULL;
    }
    fread(audio_data->sample, header.subchunk2Size, 1, inputFile);
    fclose(inputFile);
​
    return audio_data;
}
​
void audiowrite(const char* filename, struct WAVData* audio_data) {
    // 打开输出文件
    FILE* outputFile = fopen(filename, "wb");
    if (outputFile == NULL) {
        perror("文件打开失败!");
        return;
    }
​
    // 写入文件
    fwrite(&audio_data->header, sizeof(struct WAVHeader), 1, outputFile);
    fwrite(audio_data->sample, audio_data->header.subchunk2Size, 1, outputFile);
​
    // 关闭文件
    fclose(outputFile);
}

源码地址: github.com/lyc18/C-CPP…