在 C++ 中将缓冲区导出到 WAV
Posted
技术标签:
【中文标题】在 C++ 中将缓冲区导出到 WAV【英文标题】:Export buffer to WAV in C++ 【发布时间】:2015-06-24 10:27:57 【问题描述】:我有一个简单的程序,它创建一个单周期正弦波并将浮点数放入缓冲区。然后将其导出到文本文件。 但我希望能够将其导出为 WAV 文件(24 位)。有没有像文本文件一样的简单方法?
这是我到目前为止的代码:
#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;
int main ()
long double pi = 3.14159265359; // Declaration of PI
ofstream textfile; // Text object
textfile.open("sine.txt"); // Creating the txt
double samplerate = 44100.00; // Sample rate
double frequency = 200.00; // Frequency
int bufferSize = (1/frequency)*samplerate; // Buffer size
double buffer[bufferSize]; // Buffer
for (int i = 0; i <= (1/frequency)*samplerate; ++i) // Single cycle
buffer[i] = sin(frequency * (2 * pi) * i / samplerate); // Putting into buffer the float values
textfile << buffer[i] << endl; // Exporting to txt
textfile.close(); // Closing the txt
return 0; // Success
【问题讨论】:
旁注:int bufferSize = (1/frequency)*samplerate; double buffer[bufferSize]; // Buffer
这不是有效的 ANSI C++,因为数组必须用常量表达式声明为条目数。这条线的另一个问题是浮点计算不准确。 bufferSize
可能因编译器、编译器优化等而有所不同。
您要么查找 wav 格式的文档或标准并根据它进行编程,要么使用库,另请参阅***.com/questions/16075233/…
那么定义一个像 bufferSize[1000] 这样的 const 数字会更好吗?
@JesusGinard 也许使用固定大小会更好。此外,这个for (int i = 0; i <= (1/frequency)*samplerate; ++i)
是错误的,原因与我之前的评论中的相同。看到这个:isocpp.org/wiki/faq/newbie#floating-point-arith
@PaulMcKenzie 但如果我只想导出一个周期怎么办?如果我在 for 上设置一个固定值,我将有多个循环。
【参考方案1】:
首先你需要打开二进制流。
ofstream stream;
stream.open("sine.wav", ios::out | ios::binary);
接下来,您需要写出一个波头。您可以搜索以查找波形文件格式的详细信息。重要的位是数据的采样率、位深度和长度。
int bufferSize = (1/frequency)*samplerate;
stream.write("RIFF", 4); // RIFF chunk
write<int>(stream, 36 + bufferSize*sizeof(int)); // RIFF chunk size in bytes
stream.write("WAVE", 4); // WAVE chunk
stream.write("fmt ", 4); // fmt chunk
write32(stream, 16); // size of fmt chunk
write16(stream, 1); // Format = PCM
write16(stream, 1); // # of Channels
write32(stream, samplerate); // Sample Rate
write32(stream, samplerate*sizeof(int)); // Byte rate
write16(stream, sizeof(int)); // Frame size
write16(stream, 24); // Bits per sample
stream.write("data", 4); // data chunk
write32(stream, bufferSize*sizeof(int)); // data chunk size in bytes
现在标头已经不碍事了,您只需修改循环,首先将双 (-1.0,1.0) 样本转换为 32 位有符号整数。截断底部的 8 位,因为您只需要 24 位,然后写出数据。如您所知,将 24 位样本存储在 32 位字中是常见的做法,因为使用本机类型更容易跨越。
for (int i = 0; i < bufferSize; ++i) // Single cycle
double tmp = sin(frequency * (2 * pi) * i / samplerate);
int intVal = (int)(tmp * 2147483647.0) & 0xffffff00;
stream << intVal;
其他几件事:
1) 我不知道您是如何在循环中使用 <=
而不溢出 buffer
的。我把它改成了<
。
2) 再次关于缓冲区大小。我不确定您是否知道,但是对于所有频率,您不能有一个由单个周期表示的重复波形。我的意思是,对于大多数频率,如果您使用此代码并希望重复播放波形,您将在每个周期听到一个毛刺。它适用于 1kHz 这样的良好同步频率,因为每个周期恰好有 48 个样本,并且它会回到完全相同的相位。不过,999.9 Hz 将是另一回事。
【讨论】:
以上是关于在 C++ 中将缓冲区导出到 WAV的主要内容,如果未能解决你的问题,请参考以下文章
如何将 Int16Array 缓冲区保存到 wav 文件节点 js