在 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 &lt;= (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) 我不知道您是如何在循环中使用 &lt;= 而不溢出 buffer 的。我把它改成了&lt;

2) 再次关于缓冲区大小。我不确定您是否知道,但是对于所有频率,您不能有一个由单个周期表示的重复波形。我的意思是,对于大多数频率,如果您使用此代码并希望重复播放波形,您将在每个周期听到一个毛刺。它适用于 1kHz 这样的良好同步频率,因为每个周期恰好有 48 个样本,并且它会回到完全相同的相位。不过,999.9 Hz 将是另一回事。

【讨论】:

以上是关于在 C++ 中将缓冲区导出到 WAV的主要内容,如果未能解决你的问题,请参考以下文章

波形文件导出已损坏

如何将 Int16Array 缓冲区保存到 wav 文件节点 js

播放wav文件后,是不是需要删除缓冲区?

如何在 JNI 中将 char[] 转换为 ByteBuffer?

DSP FFT基频wav文件

将 C++ 数组返回到 C#