【C++】基于OpenGL的音乐可视化(六): 最终版本

经过一个多月的努力,插件的开发终于完成,等待以后测试上线,激动٩(๑>◡<๑)۶。这里,写一下利用OpenGL进行音乐可视化即音乐频谱图的绘制大致流程。

(1)音频解析

对音频解析前,一定要懂得音频的几个重要参数:采样频率,采样位数,通道数等。不懂得小伙伴可以看下面这个博客:https://blog.csdn.net/caoshangpa/article/details/51218597

这里提供一下我在进行音频解析时的代码:

//
//  main.cpp
//  PCM3.0    解码效率低 卡顿十分严重
//
//  Created by boone on 2018/8/9.
//  Copyright © 2018年 boone. All rights reserved.
//

#include <iostream>
#include <fstream>
#include <iomanip>

using namespace std;

struct wav_struct
{
    unsigned long file_size;        //文件大小
    unsigned short channel;            //通道数
    unsigned long frequency;        //采样频率
    unsigned long Bps;                //Byte率
    unsigned short sample_num_bit;    //一个样本的位数
    unsigned long data_size;        //数据大小
    unsigned char *data;            //音频数据 
};

int main(int argc, char **argv)
{
    fstream fs;
    wav_struct WAV;
    
    fs.open("/Users/boone/Desktop/Music/Seve.wav", ios::binary | ios::in);
    
    fs.seekg(0, ios::end);        //用c++常用方法获得文件大小
    WAV.file_size = fs.tellg();
    
    fs.seekg(0x14);
    fs.read((char*)&WAV.channel, sizeof(WAV.channel));
    
    fs.seekg(0x18);
    fs.read((char*)&WAV.frequency, sizeof(WAV.frequency));
    
    fs.seekg(0x1c);
    fs.read((char*)&WAV.Bps, sizeof(WAV.Bps));
    
    fs.seekg(0x22);
    fs.read((char*)&WAV.sample_num_bit, sizeof(WAV.sample_num_bit));
    
    fs.seekg(0x28);
    fs.read((char*)&WAV.data_size, sizeof(WAV.data_size));
    
    WAV.data = new unsigned char[WAV.data_size];
    
    
    fs.seekg(0x2c);
    fs.read((char *)WAV.data, sizeof(char)*WAV.data_size);
    
    cout << "文件大小为  :" << WAV.file_size << endl;
    cout << "音频通道数  :" << WAV.channel << endl;
    cout << "采样频率    :" << WAV.frequency << endl;
    cout << "Byte率      :" << WAV.Bps << endl;
    cout << "样本位数    :" << WAV.sample_num_bit << endl;
    cout << "音频数据大小:" << WAV.data_size << endl;
    cout << "最后10个数据:" << endl;
    
    for (unsigned long i =0; i<WAV.data_size; i = i + 2)
    {
        //右边为大端
        unsigned long data_low = WAV.data[i];
        unsigned long data_high = WAV.data[i + 1];
        double data_true = data_high * 256 + data_low;
        long data_complement = 0;
        //取大端的最高位(符号位)
        int my_sign = (int)(data_high / 128);

        if (my_sign == 1)
        {
            data_complement = data_true - 65536;
        }
        else
        {
            data_complement = data_true;
        }
        
        setprecision(4);
        double float_data = (double)(data_complement/(double)32768);
        printf("%f ", float_data);
        

    }
    fs.close();
    
    delete[] WAV.data;
    system("pause");
    
}

(2)傅里叶变换(FFTW)

音频解析出的数据我们并不能直接拿来绘制频谱图,解析出的数据我们称之为时域序列,利用该序列进行绘制得到的是波形图,一定要注意波形图和频谱图的区别,我们若想绘制频谱图,需要将时域序列转换为频域序列,然后利用OpenGL进行绘制。【这一段话看不懂的可以去百度或者Google补一下傅里叶变换的知识】。傅里叶变换的目的就是将音频解析出的时域序列转换为绘制频谱所需的频域序列。

网上存有大量的FFT【以下都用FFT来代替傅里叶变换】代码,GitHub上也有大量的开源FFT代码,有兴趣的可以去仔细研究一下FFT算法的实现。这里我采用的是FFTW【被誉为世界上最快的FFT】,所以如果你不是用汇编语言来实现FFT算法的话,我觉得效率都不会有FFTW高,所以推荐使用FFTW【当时我可是走了好长一段弯路来着o(╥﹏╥)o】。

    //FFTW
    fftw_complex *in,*out;
    fftw_plan p;
    in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*n);
    out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*n);
    if (in==NULL||out==NULL) {
        cout<<"ERROR: Fail to memory allocation"<<endl;
    }else{
        int  i=0;
        for(vector<float>::iterator it = vertices.begin(); it != vertices.end(); it+=2 ){
            in[i][0]=*it;
            in[i][1]=0.0;
            i++;
        }
    }
    p = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    fftw_cleanup();

(3)OpenGL绘制

音频数据我们已经成功提取,并经过FFT后转为了我们需要的频域序列。我这里将数据存入数组中,一次性传入CPU进行计算,防止在绘制过程中出现卡顿的情况,CPU计算完毕后传递给GPU进行渲染。

根据数据设置坐标,存入数组,设置顶点数组缓冲对象,顶点数组对象,绑定纹理,循环绘制。

代码比较多,这里贴出数据的存入以及循环绘制的代码【其他的都是OpenGL绘图的基础代码】:

float temp =sqrt(out[j][0]*out[j][0]+out[j][1]*out[j][1])/30000;
        j++;
        
        arr[i++]=xstart;
        arr[i++]=0.0f;
        arr[i++]=0.0f;
        
        arr[i++]=xstart;
        arr[i++]=temp;
        arr[i++]=0.0f;
        
        xstart=xstart+0.002;
        if (xstart>1.0) {
            xstart=-1.0;
        }
//绘制频谱
void drawLine()
{
    //颜色随机设置
    float redValue = 0.0f;
    float blueValue = 1.0f;
    
    for (int i=istart; i<2000+istart; i+=2) {
        
        glUniform4f(0, redValue, 1.0f, blueValue, 1.0f);
        
        if (i<=1000+istart) {
            redValue=redValue+0.002;
            blueValue=blueValue-0.002;
        }else{
            redValue=redValue-0.002;
            blueValue=blueValue+0.002;
        }
        
        glDrawArrays(GL_LINES, i, 2);
    }
    
    istart+=2000;
}

(4)实时渲染

频谱绘制出来之后难免会有些丑,所以这个时候就需要我们为频谱图化化妆了。

我对频谱的处理是同时绘制三种频谱,并分别为每种频谱绑定属于自己的着色器,在绘制线的同时改变颜色变量,实现频谱的颜色变化。在片段着色器中进行RGB颜色模型--》HSV颜色模型的转换,从而实现频谱图色相,饱和度以及亮度的控制。简单几步就将频谱图变得美美的。

片段着色器代码:

#version 330 core

out vec4 FragColor;

uniform vec4 ourColor;

uniform float u_hue=0.0;
uniform float u_saturation=1.0;
uniform float u_value=1.0;
uniform float u_contrast=0.0;

vec3 rgbtohsv(vec3 rgb)
{
    //过长省略......
    return hsv;
}
vec3 hsvtorgb(vec3 hsv)
{
    //过长省略.......
    return vec3(R, G, B);
}
void main()
{
    
    vec4 pixColor = ourColor;
    vec3 hsv;
    hsv.xyz = rgbtohsv(pixColor.rgb);
    hsv.x += u_hue;
    hsv.x = mod(hsv.x, 360.0);
    hsv.y *= u_saturation;
    hsv.z *= u_value;
    vec3 f_color = hsvtorgb(hsv);
    f_color = ((f_color - 0.5) * max(u_contrast+1.0, 0.0)) + 0.5;
    
    FragColor = vec4(f_color, pixColor.a);
    
    //FragColor = ourColor*vec4(1.0,1.0,1.0,0.5);
}

至此,音乐频谱图的绘制正式结束,经历了两个月,该插件经历了十几个版本的变迁,下面是目前开发版本的最终版:

猜你喜欢

转载自blog.csdn.net/qq_38130710/article/details/81707986