0

我想处理一个 .wav 文件,例如降低幅度;当我使用以下代码时,输​​出会失真,这并不令人愉快。

#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
    char* wav_mem;
    ifstream wav_file;
    wav_file.open("1.wav", ios::binary | ios::ate);
    int file_size = wav_file.tellg();
    wav_mem = new char[file_size];
    wav_file.seekg(0, ios::beg);
    wav_file.read(wav_mem, file_size);
    int16_t sample = 0;
    wav_file.close();
    for(int i = 44; i <= file_size; i += 2)
    {   
        sample = ((wav_mem[i + 1] << 8) | (wav_mem[i]));
        sample = (int16_t)(sample * 0.5);
        wav_mem[i] = sample;
        wav_mem[i+1] = (sample >> 8);
    }
    ofstream out_file;
    out_file.open("out.wav", ios::binary);
    out_file.write(wav_mem, file_size);
}

如何修复失真?

4

4 回答 4

4

假设您对.wav文件本身的操作是正确的(否则我不太了解它是否是问题所在),一些潜在的缺陷可能是:

  1. 收集tellgin的输出int可能会导致溢出。也许auto用来获得正确的类型?
  2. wav_mem[i+1] = (sample >> 8);i == file_size可能导致溢出访问的循环中(超出 的长度wav_mem)?

编辑:事实上,[0, file_size)由于您的wav_mem = new char[file_size];线路,您只能以定义的方式访问索引。所以当i = file_size两者都wav_mem[i]wav_mem[i+1]是UB。

于 2019-08-29T12:29:08.433 回答
1

我认为问题可能在于>>对有符号整数使用位移运算符。<<根据标准的实际行为在 C++14 中发生了变化,并将在 C++20 中再次发生变化参见“按位移位运算符”)。无论哪种方式,它都不是逻辑位移,而是算术位移。

相反,我会使用reinterpret_cast将两个字节转换为一个 16 位整数。我过去用过这样的东西:

int16_t num;
for (size_t i = 0; i < N && wav_file.read(reinterpret_cast<char*>(&num), 2); ++i) {
    audio[i] = double(num);
}

/* do stuff */

for (double x : audio) {
    num = static_cast<int16_t>(x);
    out_file.write(reinterpret_cast<char*>(&num), 2);
}

请注意,这假定了 LittleEndian 架构,因为 RIFF 使用 LittleEndian。

于 2019-08-29T12:55:46.357 回答
1

我解决了这个问题,当我试图将两个字节转换为 16 位时,我弄乱了样本,这是最终代码:

#include <iostream>
#include <fstream>
#include <string>
#include <string.h>
using namespace std;

int main()
{
    ifstream wav_file;
    ofstream out_file;
    wav_file.open("input.wav",ios::binary|ios::ate);
    size_t file_size = wav_file.tellg();
    char * wav_buf = new char[file_size];
    wav_file.seekg (0,ios::beg);
    wav_file.read (wav_buf, file_size);
    wav_file.close();
    int16_t wav_smpl(0);
    char * wav_out = new char[file_size];
    memcpy(wav_out, wav_buf, 44);
    for (size_t i = 0 ; i < file_size ; i += 2) 
    {
        memcpy(&wav_smpl , wav_buf + (i + 44) , 2);
        wav_smpl *= 3;
        memcpy(wav_out + (i + 44) , &wav_smpl , 2);
    }
    out_file.open("output.wav",ios::binary);
    out_file.write(wav_out, file_size);
    out_file.close();
    return 0;
}
于 2019-09-02T22:30:04.513 回答
0

抛开前面提到的溢出tellg和未定义的行为,wav_mem[i + 1]我认为这一行是主要问题:

sample = (int16_t)(sample * 0.5);

在幕后,sample这里转换为双倍。来回转换为 double 和 double 可能会导致轻微的(但我认为可以听到)舍入误差,我认为这可能是失真的根源。而不是这种用法:

sample /= 2;
于 2019-08-29T12:56:28.117 回答