要解码一个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);
Be First to Comment