Skip to content

基于eigen的libwav解码库

EmiyaEngine——一个测试版的玄学家生成器

要解码一个wav,首先我们要了解wav文件的结构。

wav文件分为头标识和数据流两部分,头标识部分决定了wav的一些文件参数,而数据流部分则是真正的音频数据。

因此,我们先定义一个结构体,用于储存wav结构信息

struct wav_reader
{
    std::vector<unsigned char> wav;
    uint32_t data_pos{};
    uint32_t data_length{};
    uint32_t data_left{};

    uint32_t format{};
    uint32_t sample_rate{};
    int bits_per_sample{};
    int channels{};
    uint32_t byte_rate{};
    int block_align{};

    int streamed{};
};

然后正常读取文件,注意,读取二进制文件的时候,它的数据类型是unsigned char

std::basic_ifstream<unsigned char> fin(file_path, std::ios::in | std::ios::binary);
if (!fin.is_open())
    throw std::exception("Error opening file");

fin.seekg(0, std::ios::end);
const auto sizes = fin.tellg();
wr->wav.resize(sizes);
fin.seekg(0);
fin.read(wr->wav.data(), sizes);
fin.close();

读取出来的文件,如果为wav格式,那最开始四个位的字符串,一定是"RIFF"。

之后的四个位字符串,它代表的是这组音频文件的有效长度,需要用4位的unsigned char转换到32位的uint32,也就是unsigned int。

之后的四位有效字符串,是一个代表wav文件的"WAVE"。

之后还是一串4位有效字符串,接着是一个32位的uint32,代表子串的有效长度,也就是包含在后面的wav信息的有效长度。

之后就是wav的头文件标识,其中一部分是32位的uint32,一部分是16位的uint16,也就是unsigned short,直接上代码

wr->format = read_int16(wr, ptrs);
wr->channels = read_int16(wr, ptrs);
wr->sample_rate = read_int32(wr, ptrs);
wr->byte_rate = read_int32(wr, ptrs);
wr->block_align = read_int16(wr, ptrs);
wr->bits_per_sample = read_int16(wr, ptrs);

按照顺序读取,就可以获取到整个wav文件的信息,包括格式,通道数,采样率等。

之后继续读取,应该是一个四位字符串"LIST",以及之后4位的一个int32有效长度,这一节可以整体跳过。

在之后,就是四位字符串"data",顾名思义,这是整个音频流的数据模块,以及一个4位的int32,代表了数据流的长度,我们把这段数据流整个放进一个unsigned char的数组里面,并且记录下它在文件流中的起点;这样,一个unsigned char的wav文件就应该解析完毕了

wr->data_pos = ptrs;
wr->data_length = sublength;
wr->data_left = wr->data_length;

但是解析完了数据流可能还不够,还得把unsigned char的数据,转换成eigen的数值数据,所以我们需要把读取到的文件流数据复制出来,直接进行内存转换而不是强转,来将其转换成short数据流

const auto samples = wr->data_length * 8 / wr->bits_per_sample;
std::vector<int16_t> tmp(samples);

const std::vector<unsigned char>::const_iterator first1 = wr->wav.begin() + wr->data_pos;
const std::vector<unsigned char>::const_iterator last1 = wr->wav.begin() + wr->data_pos + wr->data_length;
const std::vector data(first1, last1);
memcpy(tmp.data(), data.data(), wr->data_length);

之后将int数据量,转换到eigen的matrix数据流就行了。

std::vector<double> x(samples);
std::ranges::transform(tmp, x.begin(), [](const int16_t a) { return static_cast<double>(a) / 32767.; });

return Eigen::Map<Matrixd>(x.data(), samples / wr->channels, wr->channels);
Published in技术探究

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注