使用 fopen_s读取一个wav文件并对其求WVD并输出WVD结果不使用库函数C语言并解释代码
时间: 2024-05-07 13:23:38 浏览: 119
首先,需要了解一下WVD是什么,它代表着短时傅里叶变换(Short-Time Fourier Transform, STFT)的高阶谱,是一个二维函数而不是一维函数。其本质是将信号在时域上按窗口分段,每一段信号进行傅里叶变换得到频域表示,再用这些频域表示的数据拼接成二维矩阵。WVD可以更好地表示信号的瞬时频率和包络。
接下来是读取wav文件的代码:
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
FILE *fp;
uint32_t chunkSize;
uint16_t channelNum, bitsPerSample, blockAlign;
uint32_t sampleRate, byteRate, dataSize;
// 打开wav文件
fopen_s(&fp, "example.wav", "rb");
if (!fp) {
printf("Failed to open the file.\n");
return -1;
}
// 读取wav文件头部信息
fseek(fp, 4, SEEK_SET);
fread(&chunkSize, 4, 1, fp);
fseek(fp, 22, SEEK_SET);
fread(&channelNum, 2, 1, fp);
fseek(fp, 24, SEEK_SET);
fread(&sampleRate, 4, 1, fp);
fseek(fp, 34, SEEK_SET);
fread(&bitsPerSample, 2, 1, fp);
blockAlign = channelNum * bitsPerSample / 8;
byteRate = sampleRate * blockAlign;
fseek(fp, 40, SEEK_SET);
fread(&dataSize, 4, 1, fp);
// 读取wav数据
uint8_t *data = (uint8_t *)malloc(dataSize);
fread(data, 1, dataSize, fp);
// 关闭文件
fclose(fp);
return 0;
}
```
以上代码中,首先使用`fopen_s`函数打开wav文件,然后按照wav文件头部信息的格式读取文件头部信息,最后读取文件数据到内存中并关闭文件。需要注意的是,wav文件的头部信息格式可能因为不同的采样率、比特率等而不同,需要根据实际情况修改代码中的读取方式。
接下来是求WVD的代码:
```c
#include <math.h>
#define N 256
#define M 256
int main()
{
// 读取wav文件
...
// 计算WVD
double *wvd = (double *)malloc(N * M * sizeof(double));
double *time = (double *)malloc(M * sizeof(double));
double *freq = (double *)malloc(N * sizeof(double));
double *window = (double *)malloc(N * sizeof(double));
double *x = (double *)malloc(N * sizeof(double));
double *X = (double *)malloc(N * sizeof(double));
double *y = (double *)malloc(N * sizeof(double));
double *Y = (double *)malloc(N * sizeof(double));
double *z = (double *)malloc(N * sizeof(double));
double *Z = (double *)malloc(N * sizeof(double));
for (int m = 0; m < M; ++m) {
// 赋值时间信息
time[m] = m / (double)sampleRate;
// 读取一段信号
for (int n = 0; n < N; ++n) {
x[n] = (double)data[m * N + n] / 32768.0;
}
// 求窗口函数
for (int n = 0; n < N; ++n) {
window[n] = 0.5 - 0.5 * cos(2 * M_PI * n / (N - 1));
}
// 对信号加窗
for (int n = 0; n < N; ++n) {
x[n] *= window[n];
}
// 求X(k)
for (int k = 0; k < N; ++k) {
X[k] = 0;
for (int n = 0; n < N; ++n) {
X[k] += x[n] * cos(2 * M_PI * k * n / N);
}
}
// 求Y(k)
for (int k = 0; k < N; ++k) {
Y[k] = 0;
for (int n = 0; n < N; ++n) {
Y[k] += x[n] * sin(2 * M_PI * k * n / N);
}
}
// 求z(n)
for (int n = 0; n < N; ++n) {
z[n] = 0;
for (int k = 0; k < N; ++k) {
if (k == 0) {
z[n] += X[k] * Y[n + k] / N;
} else if (k > 0 && k <= N / 2) {
z[n] += (X[k] * Y[n + k] + X[N - k] * Y[n - k]) / N;
}
}
}
// 求Z(k)
for (int k = 0; k < N; ++k) {
Z[k] = 0;
for (int n = 0; n < N; ++n) {
if (n == 0) {
Z[k] += z[n] * cos(2 * M_PI * k * n / N);
} else if (n > 0 && n <= N / 2) {
Z[k] += (z[n] * cos(2 * M_PI * k * n / N) + z[N - n] * sin(2 * M_PI * k * n / N));
}
}
}
// 将Z(k)赋值到WVD中
for (int n = 0; n < N; ++n) {
wvd[m * N + n] = Z[n];
}
}
// 输出WVD
for (int n = 0; n < N; ++n) {
freq[n] = n * sampleRate / (double)N;
}
for (int m = 0; m < M; ++m) {
for (int n = 0; n < N; ++n) {
printf("%lf %lf %lf\n", time[m], freq[n], wvd[m * N + n]);
}
}
// 释放内存
free(data);
free(wvd);
free(time);
free(freq);
free(window);
free(x);
free(X);
free(y);
free(Y);
free(z);
free(Z);
return 0;
}
```
以上代码中,首先根据WVD的定义,定义了一个N x M的矩阵wvd,用于存储WVD结果。然后按照信号的采样率和窗口大小,将信号分为M个窗口,每个窗口长度为N。对于每个窗口,依次进行以下操作:
1. 转换到频域:将窗口内的信号进行傅里叶变换,得到实信号X(k)和虚信号Y(k)。
2. 求卷积:将实信号和虚信号进行卷积,得到z(n)。
3. 转换回时域:将z(n)进行逆傅里叶变换,得到WVD中的一行Z(k)。
4. 将Z(k)赋值到WVD的对应位置。
最后,将WVD中的结果输出到文件中即可。
需要注意的是,求WVD的过程中需要使用很多数学函数,如sin、cos等,需要包含math.h头文件。此外,需要注意数据类型的转换,如将uint8_t类型转换为double类型,以及将采样率、时间等从整型转换为浮点型。
完整代码如下:
阅读全文