如何将 Arduino 的模拟输入读数从草图转换为 .WAV
How to convert analog input readings from Arduino to .WAV from sketch
我正在尝试用 arduino 制作一个录音机。
现在设置有一个麦克风记录输入输出到 Arduino uno 的模拟输入。
如何将(时间戳、数据)读数转换为 .WAV?
我知道有更多的库能够做到这一点,但我想了解 .WAV 的结构,并希望能够在 C\C++.
中编写我自己的脚本
感谢关注!
第一步:定义wav header变量
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
这里有一些假设,比如音频质量。以上假定 CD 质量音频 16 位,44.1kHz。如果您需要其他东西,您将需要修改相关值。
您还需要注意您的 Arduino 是否真的定期记录样本,或者这是否本质上是数据的超声处理。
第 2 步:打开 Wav 文件。
#include <SPI.h>
#include <SD.h>
File wavFile;
const char* filename = "data.wav";
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
}
第 3 步:将 Header 写入 Wav 文件。
鉴于 SD Library 的组织方式,您必须将 header 的元素转换为 byte
(如果它们尚未 char
)。 Have to 可能有点强,但它确实可以更轻松地为 header 的每个元素维护正确的字节宽度,而不会弄得太多。
将它包装在它自己的函数中是有意义的。
void writeWavHeader()
{
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
第 4 步:将数据添加到 Wav 文件
wav header 告诉我们 chunkSize 和 subChunk2Size 取决于文件中的数据量。因此,如果您将数据附加到文件,这些将增长并且必须更改。
您可以通过多种方式获取有关添加数据的信息
- 一次添加一个样本
- 添加一个 缓冲区 固定大小的数据
- 存储所有数据,并全部添加到最后
你选择什么方法将取决于你的实现,我们没有太多信息。为了争论,让我们一次写一个示例。这种方法应该证明需要什么,缺点(经常在文件中跳转,大量重复)应该立即显而易见。
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
MIN_DATA_VALUE
和 MAX_DATA_VALUE
是您必须定义的内容。这一切都假设您正在使用整数数据。如果是浮点数,则需要进行一些调整。
一共
#include <SPI.h>
#include <SD.h>
int MIN_DATA_VALUE;
int MAX_DATA_VALUE;
File wavFile;
const char* filename = "data.wav";
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
writeWavHeader();
}
void loop()
{
int data = getSomeData();
writeDataToWavFile(data);
}
void writeWavHeader()
{
wavFile.seek(0);
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
总之
- 您需要知道数据的最小/最大值或限制它们。
- 您必须确保定期录制,否则您会得到伪像。
我正在尝试用 arduino 制作一个录音机。 现在设置有一个麦克风记录输入输出到 Arduino uno 的模拟输入。
如何将(时间戳、数据)读数转换为 .WAV? 我知道有更多的库能够做到这一点,但我想了解 .WAV 的结构,并希望能够在 C\C++.
中编写我自己的脚本感谢关注!
第一步:定义wav header变量
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
这里有一些假设,比如音频质量。以上假定 CD 质量音频 16 位,44.1kHz。如果您需要其他东西,您将需要修改相关值。
您还需要注意您的 Arduino 是否真的定期记录样本,或者这是否本质上是数据的超声处理。
第 2 步:打开 Wav 文件。
#include <SPI.h>
#include <SD.h>
File wavFile;
const char* filename = "data.wav";
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
}
第 3 步:将 Header 写入 Wav 文件。
鉴于 SD Library 的组织方式,您必须将 header 的元素转换为 byte
(如果它们尚未 char
)。 Have to 可能有点强,但它确实可以更轻松地为 header 的每个元素维护正确的字节宽度,而不会弄得太多。
将它包装在它自己的函数中是有意义的。
void writeWavHeader()
{
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
第 4 步:将数据添加到 Wav 文件
wav header 告诉我们 chunkSize 和 subChunk2Size 取决于文件中的数据量。因此,如果您将数据附加到文件,这些将增长并且必须更改。
您可以通过多种方式获取有关添加数据的信息
- 一次添加一个样本
- 添加一个 缓冲区 固定大小的数据
- 存储所有数据,并全部添加到最后
你选择什么方法将取决于你的实现,我们没有太多信息。为了争论,让我们一次写一个示例。这种方法应该证明需要什么,缺点(经常在文件中跳转,大量重复)应该立即显而易见。
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
MIN_DATA_VALUE
和 MAX_DATA_VALUE
是您必须定义的内容。这一切都假设您正在使用整数数据。如果是浮点数,则需要进行一些调整。
一共
#include <SPI.h>
#include <SD.h>
int MIN_DATA_VALUE;
int MAX_DATA_VALUE;
File wavFile;
const char* filename = "data.wav";
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
writeWavHeader();
}
void loop()
{
int data = getSomeData();
writeDataToWavFile(data);
}
void writeWavHeader()
{
wavFile.seek(0);
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
总之
- 您需要知道数据的最小/最大值或限制它们。
- 您必须确保定期录制,否则您会得到伪像。