近段时间在接触一个内置音频保存,音频元数据由在线网站直接文字转语音生成wav音频文件,这些在线网站生成的音频文件一般的采样率都是16KHZ的,然而大部分音频通道都采用44.1KHZ或者是48KHZ的采样率,到这里就有一个采样率不兼容的问题了,如果音频不经过处理就直接将16KHZ的音频直接走起44.1或者48KHZ采样率的音频通道直接播放音频就会被加速或者是变成其他声音了,这个时候就需要我们手动去重新处理一遍这个音频的采样率了,也就是我们通常所说的音频重采样,这里提供一份利用拉格朗日插值升采样方式的一份demo提供参考。
对于拉格朗日插值法的简单说明:在常规的数据分析当中,拉格朗日插值法是以法国十八世纪数学家约瑟夫.拉格朗日命名的一种多项式插值方法。在生活中许多的问题都可以用函数来表示某种内在联系和规律。比如对实验中的某个观察量进行观测,在若干个不同的地方或者时间得到相应的观测值,利用拉格朗日插值法重新处理这些数据会发现其恰好在各个观测点取到观测到的值。这样的多项式称为拉格朗日多项式。
数学上来讲,拉格朗日插值法可以给出一个恰好穿过二维平面上若干个已知点的多项式函数。
对于给定的n + 1 n+1n+1个点( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯ , ( x n , y n ) (x_0,y_0)\;,\;(x_1,y_1)\;,\;\cdots\;,\;(x_n,y_n)(x0,y0),(x1,y1),⋯,(xn,yn),对应于它们的次数都不超过n nn的拉格朗日多项式L LL只有一个。如果计入次数更高的多项式,则有无穷个,因为所有与L LL相差λ ( x − x 0 ) ( x − x 1 ) ⋯ ( x − x n ) \lambda(x-x_0)(x-x_1)\cdots(x-x_n)λ(x−x0)(x−x1)⋯(x−xn)的多项式都满足条件。
以下是简单的源码参考
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
// WAV文件头结构
typedef struct {
char riff[4]; // "RIFF"
uint32_t overall_size; // 文件总大小
char wave[4]; // "WAVE"
char fmt_chunk_marker[4]; // "fmt "子块标识符
uint32_t length_of_fmt; // 格式子块的长度
uint16_t format_type; // 音频格式,1 表示 PCM
uint16_t channels; // 声道数
uint32_t sample_rate; // 采样率
uint32_t byterate; // 每秒数据量
uint16_t block_align; // 数据块对齐
uint16_t bits_per_sample; // 每个样本的位数
char data_chunk_header[4]; // "data" 子块标识符
uint32_t data_size; // 数据大小
} WAVHeader;
// 拉格朗日插值函数
double lagrange_interpolation(int16_t *input, uint32_t input_size, double x, int order)
{
double result = 0.0;
int half_order = order / 2;
// 找到适合的区间
int left = (int)floor(x) - half_order;
if (left < 0)
{
left = 0;
}
else if (left + order >= input_size)
{
left = input_size - order - 1;
}
// 拉格朗日插值计算
for (int i = 0; i <= order; i++)
{
double term = 1.0;
for (int j = 0; j <= order; j++)
{
if (i != j)
{
term *= (x - (left + j)) / (double)((left + i) - (left + j));
}
}
result += term * input[left + i];
}
return result;
}
// 采样率转换函数
void resample_lagrange(int16_t *input, int16_t *output, uint32_t input_size, uint32_t output_size, int order)
{
double ratio = (double)(input_size - 1) / (output_size - 1);
for (uint32_t i = 0; i < output_size; i++)
{
double src_index = i * ratio;
output[i] = (int16_t)round(lagrange_interpolation(input, input_size, src_index, order));
}
}
int main()
{
// 打开原始16kHz的WAV文件
FILE *input_wav = fopen("./Bt_connect.wav", "rb");
if (input_wav == NULL)
{
printf("无法打开WAV文件\n");
return 1;
}
// 读取WAV文件头
WAVHeader input_header;
fread(&input_header, sizeof(WAVHeader), 1, input_wav);
// 打印WAV文件头信息
printf("声道数: %d\n", input_header.channels);
printf("采样率: %d\n", input_header.sample_rate);
printf("每秒字节数: %d\n", input_header.byterate);
printf("每样本位数: %d\n", input_header.bits_per_sample);
printf("数据大小: %d\n", input_header.data_size);
// 验证是否为16kHz采样率
if (input_header.sample_rate != 16000) {
printf("该WAV文件不是16kHz采样率\n");
fclose(input_wav);
return 1;
}
// 读取音频数据
int16_t *input_data = (int16_t *)malloc(input_header.data_size);
if (input_data == NULL) {
printf("内存分配失败\n");
fclose(input_wav);
return 1;
}
fread(input_data, input_header.data_size, 1, input_wav);
fclose(input_wav);
// 计算输出的音频样本数(采样率从16kHz到44.1kHz)
uint32_t input_sample_count = input_header.data_size / (input_header.bits_per_sample / 8);
uint32_t output_sample_count = (uint32_t)((double)input_sample_count * 44100 / 16000);
int16_t *output_data = (int16_t *)malloc(output_sample_count * sizeof(int16_t));
if (output_data == NULL) {
printf("内存分配失败\n");
free(input_data);
return 1;
}
printf("input_sample_count = %d, output_sample_count = %d\r\n", input_sample_count, output_sample_count);
// 进行拉格朗日插值采样率转换,使用4阶插值
resample_lagrange(input_data, output_data, input_sample_count, output_sample_count, 4);
// 创建新的44.1kHz WAV文件
FILE *output_wav = fopen("output_44.1kHz.wav", "wb");
if (output_wav == NULL) {
printf("无法创建输出文件\n");
free(input_data);
free(output_data);
return 1;
}
// 填充并写入WAV文件头
WAVHeader output_header = input_header;
output_header.sample_rate = 44100;
output_header.byterate = 44100 * output_header.channels * (output_header.bits_per_sample / 8);
output_header.data_size = output_sample_count * (output_header.bits_per_sample / 8);
output_header.overall_size = output_header.data_size + sizeof(WAVHeader) - 8;
fwrite(&output_header, sizeof(WAVHeader), 1, output_wav);
// 打印WAV文件头信息
printf("声道数: %d\n", output_header.channels);
printf("采样率: %d\n", output_header.sample_rate);
printf("每秒字节数: %d\n", output_header.byterate);
printf("每样本位数: %d\n", output_header.bits_per_sample);
printf("数据大小: %d\n", output_header.data_size);
printf("input_sample_count = %d, output_sample_count = %d\r\n", input_sample_count, output_sample_count);
// 写入转换后的音频数据
fwrite(output_data, output_sample_count, 1, output_wav);
// 关闭文件并释放内存
fclose(output_wav);
free(input_data);
free(output_data);
printf("采样率转换完成,输出为44.1kHz的WAV文件\n");
return 0;
}