我正在用 C 编写一个 .wav PCM 文件头,但我仍然难以处理的一个部分是文件的每个样本格式参数的位数。现在我只添加了对 8 位和 16 位 PCM 的支持(将音频样本数据存储在
int16_t*
或 int8_t*
byteArray
中),但如果能够读取在将文件数据加载到内存之前,编写一堆函数来处理每个字节大小(writeToWav8()
,writeToWav16()
,writeToWav32()
)感觉非常天真。我能想到的唯一解决方案是
使用 1 字节整数的固定数组并找到一种方法将较大的样本动态“打包”到数组中(例如,将 01000110 装入 0100、0110),我之前尝试过,但最终最终陷入困境,因为它不断导致一些问题我找不到根本原因的精度损失(另外考虑到无论如何我都必须为不同的字节大小编写不同的处理程序)
结构现在看起来像这样:
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(并使用适当的函数)。
作为数据部分。对于其余部分,您的代码中有很多重复,因此只需将它们放入标记的联合中即可
typedef struct DataChunk {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
union {
int8_t byteArray[];
int16_t byteArray[];
};
} DATA_CHUNK;
typedef struct WaveFile {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK DATA;
} WAV_FILE;
这个功能在进入 C 标准之前就已经很常见了。例如,在 GCC 中,它是结构末尾长度为零的数组
,而在 MSVC 中,不能拥有长度为零的数组,因此他们使用 大小为 1 的数组。我还使用了匿名结构功能来避免另一个不必要的字段引用 分配结构时,您需要为
整个标头和数据指定足够的内存
// Allocate memory for the header
WAV_FILE *wav = malloc(sizeof(*wav));
// Read the header
if (fread(wav, sizeof(WAV_FILE), 1, inputfile) == 1) {
// Resize the struct to make room for the data
WAV_FILE *newWav = realloc(sizeof(*wav) + wav->DATA.Subchunk2Size);
if (newWav) {
wav = newWav;
// Do something with the data
}
}
我不是语言律师,所以我不确定灵活数组成员是否位于联合或嵌套结构中是否允许。但如果不是,那么您可以轻松地重新组合结构,将数组移动到最外层结构的末尾,例如像这样
typedef struct DataChunk {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
} DATA_CHUNK_HEADER;
typedef struct WaveFile {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_HEADER DATA_HEADER;
union {
int8_t byteArray[];
int16_t byteArray[];
};
} WAV_FILE;
// or
typedef union WaveFile2 {
struct {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_HEADER DATA_HEADER;
int8_t byteArray[];
};
struct {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_HEADER DATA_HEADER;
int16_t byteArray[];
};
} WAV_FILE2;
int8_t *byteArray;
并仅使用
int16_t *byteArray;
,当文件是 8 位文件时,可以将其转换为 char*
或 int8_t*
,而不违反别名规则当然,您还需要像尼尔森评论的那样注意字节顺序