处理 .wav 文件期间输出失真
distorted output during processing .wav file
我想处理一个.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);
}
如何修复失真?
假设您对 .wav
文件本身的操作是合理的(否则我不太了解它是否是问题所在)一些潜在的缺陷可能是:
- 在
int
中收集 tellg
的输出可能会导致溢出。也许使用 auto
来获得正确的类型?
wav_mem[i+1] = (sample >> 8);
when i == file_size
in the loop that might cause overflow access (beyond the length of wav_mem
) ?
编辑:
实际上,由于 wav_mem = new char[file_size];
行,您只能以定义的方式访问 [0, file_size)
索引。所以当 i = file_size
时 wav_mem[i]
和 wav_mem[i+1]
都将是 UB。
我认为问题可能在于对有符号整数使用位移运算符 >>
。根据标准 has changed in C++14 and is going to change again in C++20 (cf. "Bitwise shift operators") 的 <<
的实际行为。无论哪种方式,它都不是逻辑位移而是算术位移。
相反,我会使用 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。
撇开前面提到的 tellg
中的溢出和 wav_mem[i + 1]
中的未定义行为,我认为这一行是主要问题:
sample = (int16_t)(sample * 0.5);
在幕后,sample
在此处转换为双精度。来回转换为 double 和从 double 可能会导致轻微的(但我想可以听到)舍入错误,我认为这可能是失真的来源。而不是这个使用:
sample /= 2;
我解决了这个问题,当我试图将两个字节转换为 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;
}
我想处理一个.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);
}
如何修复失真?
假设您对 .wav
文件本身的操作是合理的(否则我不太了解它是否是问题所在)一些潜在的缺陷可能是:
- 在
int
中收集tellg
的输出可能会导致溢出。也许使用auto
来获得正确的类型? wav_mem[i+1] = (sample >> 8);
wheni == file_size
in the loop that might cause overflow access (beyond the length ofwav_mem
) ?
编辑:
实际上,由于 wav_mem = new char[file_size];
行,您只能以定义的方式访问 [0, file_size)
索引。所以当 i = file_size
时 wav_mem[i]
和 wav_mem[i+1]
都将是 UB。
我认为问题可能在于对有符号整数使用位移运算符 >>
。根据标准 has changed in C++14 and is going to change again in C++20 (cf. "Bitwise shift operators") 的 <<
的实际行为。无论哪种方式,它都不是逻辑位移而是算术位移。
相反,我会使用 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。
撇开前面提到的 tellg
中的溢出和 wav_mem[i + 1]
中的未定义行为,我认为这一行是主要问题:
sample = (int16_t)(sample * 0.5);
在幕后,sample
在此处转换为双精度。来回转换为 double 和从 double 可能会导致轻微的(但我想可以听到)舍入错误,我认为这可能是失真的来源。而不是这个使用:
sample /= 2;
我解决了这个问题,当我试图将两个字节转换为 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;
}