我正在用 C 语言编写 .wav PCM 文件头,但我仍然无法处理的一个部分是文件的每样本位数格式参数。目前,我只添加了对 8 位和 16 位 PCM 的支持(将音频样本数据存储在int16_t*
或 中int8_t*
byteArray
),但如果能够在将数据加载到内存之前读取文件的样本大小就好了,而编写一堆函数来处理每个字节大小(writeToWav8()
、writeToWav16()
、writeToWav32()
)感觉非常幼稚。
我能想到的唯一解决办法是
- 使用一个固定的 1 字节整数数组,并找到一种将更大的样本动态“打包”到数组中的方法(例如,将 01000110 放入 0100、0110 中),我之前尝试过但最终陷入困境,因为它一直导致某种精度损失,而我找不到根本原因(另外考虑到我必须为不同的字节大小编写不同的处理程序)
- 限制程序只能读取/写入 8、16 和 32 位 PCM,这感觉有点像逃避。
有没有办法更彻底地处理这个问题?
该结构现在如下所示:
typedef struct RiffHeader {
// basic formatting info for the file, not relevant
} RIFF_CHUNK;
typedef struct FmtHeader {
uint32_t Subchunk1ID;
// all of these determine how to read the audio
uint32_t Subchunk1Size;
uint16_t AudioFormat;
uint16_t NumChannels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample; // bits per sample, determines how large each bit is in the data array
} FMT_CHUNK;
typedef struct DataChunk8 {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
int8_t *byteArray; // pointer to start of data
// data size is equal to subchunk2size
} DATA_CHUNK_8;
typedef struct DataChunk16 {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
int16_t *byteArray; // pointer to start of data
// data size is equal to subchunk2size
} DATA_CHUNK_16;
typedef struct WaveFile8 {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_8 DATA;
} WAV_FILE_8;
typedef struct WaveFile_16 {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_16 DATA;
} WAV_FILE_16;
接下来是一些辅助函数,用于输入和/或输出WAV_FILE_8*
或WAV_FILE_16*
*另外,我对文件读取有点困惑,因为我目前正在使用两个功能:
WAV_FILE_8* readFromFile8(const char* path)
WAV_FILE_16* readFromFile16(const char* path)
它们旨在完成您所期望的操作,但我不确定在读取未知文件时如何确定并决定返回适当大小的 WAV(并使用适当的函数)。
您可以简单地将灵活数组成员用于数据部分。对于剩余部分,您的代码中有很多重复,因此只需将它们放在标记联合中即可
这个特性在进入 C 标准之前就很常见了。例如,在 GCC 中,结构体的末尾有一个长度为零的数组,而在 MSVC 中,不能有长度为零的数组,所以他们改用大小为 1 的数组。我还使用了匿名结构体特性来避免另一个不必要的字段引用
分配结构时,你需要为整个标头和数据指定足够的内存
我不是语言律师,所以我不确定是否允许将灵活数组成员放在联合或嵌套结构中。但如果不允许,则您可以轻松地重新组合结构以将数组移动到最外层结构的末尾,例如像这样
或者,您可以完全删除
int8_t *byteArray;
并仅使用int16_t *byteArray;
可以转换为char*
或int8_t*
当文件为 8 位时不违反别名规则当然,你还需要注意字节序,正如尼尔森评论的那样