【语音学习记录】WAV 文件读写操作

【语音学习记录】WAV 文件读写操作(C++)

WAV 是常见的声音文件格式之一,WAV 是最常见的声音文件格式之一,是微软公司专门为 Windows 开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。但 WAV 文件有一个致命的缺点,就是它所占用的磁盘空间太大(每分钟的音乐大约需要 12 兆磁盘空间)。它符合资源互换文件格式(RIFF)规范,用于保存 Windows 平台的音频信息资源,被 Windows 平台及其应用程序所广泛支持。Wave 格式支持 MSADPCMCCITT A A A律、CCITT μ \mu μ律和其他压缩算法,支持多种音频位数、采样频率和声道,是 PC 机上最为流行的声音文件格式;但其文件尺寸较大,多用于存储简短的声音片段。

见百度百科

1、WAV 文件结构

WAV 是符合 RIFF 标准的多媒体文件,其文件结构可以如下:
在这里插入图片描述

如上图,WAV 文件结构包含共 5 块部分。首先是一个 RIFF 块,指明该文件是符合 RIFF 标准的文件;接着是一个 FourCCWAVE,该文件为 WAV 文件;fmt 块包含了音频的一些属性:采样率、码率、声道等;fact 块是一个可选块,不是 PCM 数据格式的需要该块;最后 data 块,则包含了音频的 PCM 数据。

实际上,可以将一个 WAV 文件看着由两部分组成:文件头和 PCM 数据,其中 WAV 文件头各字段如下:

偏移地址字节数数据类型字段名称字段说明
00 H4字符文档标识大写字符串 “RIFF”,标明该文件为有效的 RIFF 格式文档
04 H4长整型文件数据长度从下一个字段首地址开始到文件末尾的总字节数。
08 H4字符文件格式类型所有 WAV 格式的文件此处为字符串 WAVE,标明该文件是 WAV 格式文件。
0C H4字符格式块标识小写字符串,“fmt ”
10 H4长整型格式块长度其数值不确定,取决于编码格式。可以是 16、18、20、40 等。
14 H2整型编码格式代码常见的 WAV 文件使用 PCM 脉冲编码调制格式,该数值通常为 1 。
16 H2整型声道个数单声道为 1 ,立体声或双声道为 2 。
18 H4长整型采样频率每个声道单位时间采样次数。(常见的有 11025,22050 和 44100 kHZ
1C H4长整型数据传输速率该数值为:声道数 * 采样频率 * 每样本的数据位数 / 8 。(播放软件利用此值可以估计缓冲区的大小)
20 H2整型数据块对齐单位采样帧大小。该数值为:声道数 * 位数 / 8 。(播放软件需要一次处理多个该值大小的字节数据)
22 H2整型采样位数存储每个采样值所用的二进制位数。(常见的位数有 4、8、12、16、24、32
24 H---该部分是对基本格式快的扩充部分
2、下面以 C++ 为例进行文件写入的准备
  1. 首先,定义文件头结构体:

    struct WaveHeader {
        /* 文件头部分 */
        char MainChunk[4];          /* 文档标识 (默认为 “RIFF”)*/
        uint32_t LenFile;           /* 文件数据长度 (默认为 0)*/
        char ChunkType[4];          /* 文件格式类型 (默认为 “WAVE”)*/
        char SubChunk[4];           /* 格式块标识 (默认为 “fmt ”)*/
        uint32_t LenSubChunk;       /* 格式块长度 (默认为 16)*/
        uint16_t FormatTag;         /* 编码格式代码 (默认为 1)*/
        uint16_t Channels;          /* 声道个数 (可选值为 1,2)*/
        uint32_t SamplesRate;       /* 采样频率 */
        uint32_t AvgBytesPerSec;    /* 数据传输速率 */
        uint16_t BlockAlign;        /* 数据块对齐单位 */
        uint16_t BitsPerSample;     /* 采样位数 */
        /* PCM 数据部分 */
        char DataChunk[4];          /* "data" */
        uint32_t DataLen;           /* Length of data */
    };
    
  2. 定义读取数据函数:

    /* 定义读取数据函数 */
    int read_wav(FILE *input, int *buffer, int readNum) {
        int readLen = fread(buffer, 2 * sizeof(short), readNum, input);
        // 结束文件读取,关闭打开的音频文件
        cout << "A total of " << readLen << " items of data were obtained by reading the file." << endl;
        return readLen;
    }
    
3、完成上述准备工作后,开始实现 WAV 文件的复写
  1. 首先,本次测试所使用的 WAV 文件的基本数据如下(使用 Adobe audition 打开获取的信息):

    持续时间(s)采样率(Hz)声道位深度样点数
    10.844100单声道16476280

在这里插入图片描述

  1. 根据原始文件数据定义复写文件的头文件数据:

    uint32_t sr = 44100;
    uint32_t bits = 16;
    uint16_t channels = 1;
    uint32_t samples = 476280 * bits / 8;
    
    const WaveHeader WaveHdr = {
        {'R', 'I', 'F', 'F'},
        uint32_t(samples + sizeof(WaveHeader)),
        {'W', 'A', 'V', 'E'},
        {'f', 'm', 't', ' '},
        bits, 1, channels,
        sr, sr * channels * bits / 8,
        uint16_t(channels * bits / 8),
        uint16_t(bits),
        {'d', 'a', 't', 'a'}, samples
    };
    

    tips:这里有尝试过将结构体 WaveHeader 中的 LenFileDataLen 修改为较大的值,对复写结果并没有影响。

  2. 打开读入数据文件以及写入数据文件:

    string input_path = "数据数据文件路径";
    string output_dir = "输出数据文件路径";
    
    // 打开文件用于数据读取
    FILE *iFIle = fopen(input_path.c_str(), "rb");
    fseek(iFIle, sizeof(WaveHeader), SEEK_SET);
    
    // 打开文件用于数据写入
    FILE *oFile = fopen(output_dir.c_str(), "wb");
    fwrite(&WaveHdr, sizeof(WaveHeader), 1, oFile);
    

    tips:这里需要注意当前所进行的复写过程,仅仅复写文件的 PCM 数据部分,因此输入数据部分需要跳过头文件部分,及代码中使用的fseek函数

  3. 定义一些基本变量,包含readSize:每次读取的数据数量readData:读取到的数据数量buffer:用于存放读取到的数据内容

    int readSize = 4096;
    int readData = readSize;
    int *buffer = new int[readSize];
    
  4. 使用循环,读取文件数据并写入至输出文件中:

    for (int i = 0; readData == readSize; ++i) {
        readData = read_wav(iFIle, buffer, readSize);
        fwrite(buffer, 2 * sizeof(short), readData, oFile);
    }
    
  5. 源码如下:

    #include <iostream>
    
    struct WaveHeader {
        /* 文件头部分 */
        char MainChunk[4];          /* 文档标识 (默认为 “RIFF”)*/
        uint32_t LenFile;           /* 文件数据长度 (默认为 0)*/
        char ChunkType[4];          /* 文件格式类型 (默认为 “WAVE”)*/
        char SubChunk[4];           /* 格式块标识 (默认为 “fmt ”)*/
        uint32_t LenSubChunk;       /* 格式块长度 (默认为 16)*/
        uint16_t FormatTag;         /* 编码格式代码 (默认为 1)*/
        uint16_t Channels;          /* 声道个数 (可选值为 1,2)*/
        uint32_t SamplesRate;       /* 采样频率 */
        uint32_t AvgBytesPerSec;    /* 数据传输速率 */
        uint16_t BlockAlign;        /* 数据块对齐单位 */
        uint16_t BitsPerSample;     /* 采样位数 */
        /* PCM 数据部分 */
        char DataChunk[4];          /* "data" */
        uint32_t DataLen;           /* Length of data */
    };
    
    using namespace std;
    
    int read_wav(FILE *iFileReceiver, int *buffer, int readNum) {
        int readLen = fread(buffer, 2 * sizeof(short), readNum, iFileReceiver);
        // 结束文件读取,关闭打开的音频文件
        cout << "A total of " << readLen << " items of data were obtained by reading the file." << endl;
        return readLen;
    }
    
    int main() {
        uint32_t sr = 44100;
        uint32_t bits = 16;
        uint16_t channels = 1;
        uint32_t samples = 476280 * bits / 8;
    
        const WaveHeader WaveHdr = {
                {'R', 'I', 'F', 'F'},
                uint32_t(samples + sizeof(WaveHeader)),
                {'W', 'A', 'V', 'E'},
                {'f', 'm', 't', ' '},
                bits, 1, channels,
                sr, sr * channels * bits / 8,
                uint16_t(channels * bits / 8),
                uint16_t(bits),
                {'d', 'a', 't', 'a'}, samples
        };
    
        string input_path = "数据数据文件路径";
    	string output_dir = "输出数据文件路径";
    
        // 打开文件用于数据读取
        FILE *iFIle = fopen(input_path.c_str(), "rb");
        fseek(iFIle, sizeof(WaveHeader), SEEK_SET);
    
        // 打开文件用于数据写入
        FILE *oFile = fopen(output_dir.c_str(), "wb");
        fwrite(&WaveHdr, sizeof(WaveHeader), 1, oFile);
    
        int readSize = 4096;
        int readData = readSize;
        int *buffer = new int[readSize];
    
        // 读取文件数据并写入至输出文件
        for (int i = 0; readData == readSize; ++i) {
            readData = read_wav(iFIle, buffer, readSize);
            fwrite(buffer, 2 * bits / 8, readData, oFile);
        }
        
        fclose(oFile);
        oFile = NULL;
    
        return 0;
    }
    
4、对于未知数据长度的 WAV 文件写入

对于 WAV 的音频写入数据,很多音频数据是以数据流的形式不断向文件中写入的,这是头文件的文件长度和数据长度就变为了动态的值,在写入这类数据时,可以采用fseek对头文件的内容进行修改,如下(将复写部分的第五步修改如下即可):

int *buffer = new int[readSize];

// 读取文件数据并写入至输出文件
for (int i = 0; readData == readSize; ++i) {
    readData = read_wav(iFIle, buffer, readSize);
    fwrite(buffer, 2 * bits / 8, readData, oFile);
}

// 音频文件长度
const uint32_t iFileLength = iBytesWritten - 8;
fseek(fOutput, 4 /* offset */, SEEK_SET /* origin */);
(void)fwrite((const void*)&iFileLength, size_t(4), size_t(1), fOutput);

// 音频数据长度
const uint32_t iDataLength = iBytesWritten - sizeof(CWaveHdr);
fseek(fOutput, 40 /* offset */, SEEK_SET /* origin */);
(void)fwrite((const void*)&iDataLength, size_t(4), size_t(1), fOutput);


fclose(oFile);
oFile = NULL;
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Qt中,可以使用QAudioDecoder和QAudioEncoder类来实现.wav文件的读写操作。 读取.wav文件: ```cpp QAudioDecoder decoder; decoder.setSourceFilename("audio.wav"); // 设置要读取的.wav文件名 QObject::connect(&decoder, &QAudioDecoder::bufferReady, [&](const QAudioBuffer& buffer){ // 处理读取到的音频数据 const qint16* data = buffer.constData<qint16>(); int sampleCount = buffer.sampleCount(); int channelCount = buffer.channelCount(); int sampleRate = buffer.format().sampleRate(); // 进行相应的处理 }); decoder.start(); // 开始读取.wav文件 ``` 写入.wav文件: ```cpp QAudioEncoderSettings settings; settings.setCodec("audio/pcm"); settings.setSampleRate(44100); settings.setChannelCount(2); settings.setSampleSize(16); settings.setQuality(QMultimedia::HighQuality); QAudioEncoder encoder; encoder.setAudioSettings(settings); encoder.setOutputLocation(QUrl::fromLocalFile("output.wav")); // 设置输出的.wav文件名 QObject::connect(&encoder, &QAudioEncoder::encodedAudioAvailable, [&](const QAudioEncoderControl::EncodedAudioBuffer& buffer){ // 处理编码后的音频数据 const QByteArray& data = buffer.data(); // 进行相应的处理 }); encoder.record(); // 开始录制音频数据 // 在需要录制的地方,使用encoder.encode()将音频数据传递给编码器 encoder.stop(); // 停止录制音频数据,并将编码后的数据写入.wav文件 ``` 请注意,以上代码只是演示了如何使用Qt进行.wav文件的读写操作,具体的实现可能还需要根据实际需求进行适当的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值