zoukankan      html  css  js  c++  java
  • 基于android的实时音频频谱仪

    前一段实习,本来打算做c++,到了公司发现没啥项目,于是乎转行做了android,写的第一个程序竟然要我处理信号,咱可是一心搞计算机的,没接触过信号的东西,什么都没接触过,于是乎, 找各种朋友,各种熟人,现在想想,专注语言是不对的,语言就是一工具,关键还是业务,算法。好了,废话不多说,上程序,注释都很详细,应该能看懂。

            分析声音,其实很简单,就是运用傅里叶变换,将声音信号由时域转化到频域(程序用的是快速傅里叶变换,比较简单),为啥要这样,好处多多,不细讲,公司里的用处是为了检测手机发出声音的信号所在的频率集中范围。

    第一个类,复数的计算,用到加减乘,很简单。

     

    [java] view plaincopy
     
    1. package com.mobao360.sunshine;  
    2. //复数的加减乘运算  
    3. public class Complex {  
    4.     public double real;  
    5.     public double image;  
    6.       
    7.     //三个构造函数  
    8.     public Complex() {  
    9.         // TODO Auto-generated constructor stub  
    10.         this.real = 0;  
    11.         this.image = 0;  
    12.     }  
    13.   
    14.     public Complex(double real, double image){  
    15.         this.real = real;  
    16.         this.image = image;  
    17.     }  
    18.       
    19.     public Complex(int real, int image) {  
    20.         Integer integer = real;  
    21.         this.real = integer.floatValue();  
    22.         integer = image;  
    23.         this.image = integer.floatValue();  
    24.     }  
    25.       
    26.     public Complex(double real) {  
    27.         this.real = real;  
    28.         this.image = 0;  
    29.     }  
    30.     //乘法  
    31.     public Complex cc(Complex complex) {  
    32.         Complex tmpComplex = new Complex();  
    33.         tmpComplex.real = this.real * complex.real - this.image * complex.image;  
    34.         tmpComplex.image = this.real * complex.image + this.image * complex.real;  
    35.         return tmpComplex;  
    36.     }  
    37.     //加法  
    38.     public Complex sum(Complex complex) {  
    39.         Complex tmpComplex = new Complex();  
    40.         tmpComplex.real = this.real + complex.real;  
    41.         tmpComplex.image = this.image + complex.image;  
    42.         return tmpComplex;  
    43.     }  
    44.     //减法  
    45.     public Complex cut(Complex complex) {  
    46.         Complex tmpComplex = new Complex();  
    47.         tmpComplex.real = this.real - complex.real;  
    48.         tmpComplex.image = this.image - complex.image;  
    49.         return tmpComplex;  
    50.     }  
    51.     //获得一个复数的值  
    52.     public int getIntValue(){  
    53.         int ret = 0;  
    54.         ret = (int) Math.round(Math.sqrt(this.real*this.real - this.image*this.image));  
    55.         return ret;  
    56.     }  
    57. }  

     

            这个类是有三个功能,第一,采集数据;第二,进行快速傅里叶计算;第三,绘图。

            采集数据用AudioRecord类,网上讲解这个类的蛮多的,搞清楚构造类的各个参数就可以。

            绘图用的是SurfaceView Paint Canvas三个类,本人也是参考网络达人的代码

     

    [java] view plaincopy
     
    1. package com.mobao360.sunshine;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.lang.Short;  
    5.   
    6.   
    7. import android.content.Context;  
    8. import android.graphics.Canvas;  
    9. import android.graphics.Color;  
    10. import android.graphics.DashPathEffect;  
    11. import android.graphics.Paint;  
    12. import android.graphics.Path;  
    13. import android.graphics.PathEffect;  
    14. import android.graphics.Rect;  
    15. import android.media.AudioRecord;  
    16. import android.util.Log;  
    17. import android.view.SurfaceView;  
    18.   
    19. public class AudioProcess {  
    20.     public static final float pi= (float) 3.1415926;  
    21.     //应该把处理前后处理后的普线都显示出来  
    22.     private ArrayList<short[]> inBuf = new ArrayList<short[]>();//原始录入数据  
    23.     private ArrayList<int[]> outBuf = new ArrayList<int[]>();//处理后的数据  
    24.     private boolean isRecording = false;  
    25.   
    26.     Context mContext;  
    27.     private int shift = 30;  
    28.     public int frequence = 0;  
    29.       
    30.     private int length = 256;  
    31.     //y轴缩小的比例  
    32.     public int rateY = 21;  
    33.     //y轴基线  
    34.     public int baseLine = 0;  
    35.     //初始化画图的一些参数  
    36.     public void initDraw(int rateY, int baseLine,Context mContext, int frequence){  
    37.         this.mContext = mContext;  
    38.         this.rateY = rateY;  
    39.         this.baseLine = baseLine;  
    40.         this.frequence = frequence;  
    41.     }  
    42.     //启动程序  
    43.     public void start(AudioRecord audioRecord, int minBufferSize, SurfaceView sfvSurfaceView) {  
    44.         isRecording = true;  
    45.         new RecordThread(audioRecord, minBufferSize).start();  
    46.         new DrawThread(sfvSurfaceView).start();  
    47.     }  
    48.     //停止程序  
    49.     public void stop(SurfaceView sfvSurfaceView){  
    50.         isRecording = false;  
    51.         inBuf.clear();  
    52.     }  
    53.       
    54.     //录音线程  
    55.     class RecordThread extends Thread{  
    56.         private AudioRecord audioRecord;  
    57.         private int minBufferSize;  
    58.           
    59.         public RecordThread(AudioRecord audioRecord,int minBufferSize){  
    60.             this.audioRecord = audioRecord;  
    61.             this.minBufferSize = minBufferSize;  
    62.         }  
    63.           
    64.         public void run(){  
    65.             try{  
    66.                 short[] buffer = new short[minBufferSize];  
    67.                 audioRecord.startRecording();  
    68.                 while(isRecording){  
    69.                     int res = audioRecord.read(buffer, 0, minBufferSize);  
    70.                     synchronized (inBuf){  
    71.                         inBuf.add(buffer);  
    72.                     }  
    73.                     //保证长度为2的幂次数  
    74.                     length=up2int(res);  
    75.                     short[]tmpBuf = new short[length];  
    76.                     System.arraycopy(buffer, 0, tmpBuf, 0, length);  
    77.                       
    78.                     Complex[]complexs = new Complex[length];  
    79.                     int[]outInt = new int[length];  
    80.                     for(int i=0;i < length; i++){  
    81.                         Short short1 = tmpBuf[i];  
    82.                         complexs[i] = new Complex(short1.doubleValue());  
    83.                     }  
    84.                     fft(complexs,length);  
    85.                     for (int i = 0; i < length; i++) {  
    86.                         outInt[i] = complexs[i].getIntValue();  
    87.                     }  
    88.                     synchronized (outBuf) {  
    89.                         outBuf.add(outInt);  
    90.                     }  
    91.                 }  
    92.                 audioRecord.stop();  
    93.             }catch (Exception e) {  
    94.                 // TODO: handle exception  
    95.                 Log.i("Rec E",e.toString());  
    96.             }  
    97.               
    98.         }  
    99.     }  
    100.   
    101.     //绘图线程  
    102.     class DrawThread extends Thread{  
    103.         //画板  
    104.         private SurfaceView sfvSurfaceView;  
    105.         //当前画图所在屏幕x轴的坐标  
    106.         //画笔  
    107.         private Paint mPaint;  
    108.         private Paint tPaint;  
    109.         private Paint dashPaint;  
    110.         public DrawThread(SurfaceView sfvSurfaceView) {  
    111.             this.sfvSurfaceView = sfvSurfaceView;  
    112.             //设置画笔属性  
    113.             mPaint = new Paint();  
    114.             mPaint.setColor(Color.BLUE);  
    115.             mPaint.setStrokeWidth(2);  
    116.             mPaint.setAntiAlias(true);  
    117.               
    118.             tPaint = new Paint();  
    119.             tPaint.setColor(Color.YELLOW);  
    120.             tPaint.setStrokeWidth(1);  
    121.             tPaint.setAntiAlias(true);  
    122.               
    123.             //画虚线  
    124.             dashPaint = new Paint();  
    125.             dashPaint.setStyle(Paint.Style.STROKE);  
    126.             dashPaint.setColor(Color.GRAY);  
    127.             Path path = new Path();  
    128.             path.moveTo(0, 10);  
    129.             path.lineTo(480,10);   
    130.             PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);  
    131.             dashPaint.setPathEffect(effects);  
    132.         }  
    133.           
    134.         @SuppressWarnings("unchecked")  
    135.         public void run() {  
    136.             while (isRecording) {  
    137.                 ArrayList<int[]>buf = new ArrayList<int[]>();  
    138.                 synchronized (outBuf) {  
    139.                     if (outBuf.size() == 0) {  
    140.                         continue;  
    141.                     }  
    142.                     buf = (ArrayList<int[]>)outBuf.clone();  
    143.                     outBuf.clear();  
    144.                 }  
    145.                 //根据ArrayList中的short数组开始绘图  
    146.                 for(int i = 0; i < buf.size(); i++){  
    147.                     int[]tmpBuf = buf.get(i);  
    148.                     SimpleDraw(tmpBuf, rateY, baseLine);  
    149.                 }  
    150.                   
    151.             }  
    152.         }  
    153.           
    154.         /**  
    155.          * 绘制指定区域  
    156.          *   
    157.          * @param start  
    158.          *            X 轴开始的位置(全屏)  
    159.          * @param buffer  
    160.          *             缓冲区  
    161.          * @param rate  
    162.          *            Y 轴数据缩小的比例  
    163.          * @param baseLine  
    164.          *            Y 轴基线  
    165.          */   
    166.   
    167.         private void SimpleDraw(int[] buffer, int rate, int baseLine){  
    168.             Canvas canvas = sfvSurfaceView.getHolder().lockCanvas(  
    169.                     new Rect(0, 0, buffer.length,sfvSurfaceView.getHeight()));  
    170.             canvas.drawColor(Color.BLACK);  
    171.             canvas.drawText("幅度值", 0, 3, 2, 15, tPaint);  
    172.             canvas.drawText("原点(0,0)", 0, 7, 5, baseLine + 15, tPaint);  
    173.             canvas.drawText("频率(HZ)", 0, 6, sfvSurfaceView.getWidth() - 50, baseLine + 30, tPaint);  
    174.             canvas.drawLine(shift, 20, shift, baseLine, tPaint);  
    175.             canvas.drawLine(shift, baseLine, sfvSurfaceView.getWidth(), baseLine, tPaint);  
    176.             canvas.save();  
    177.             canvas.rotate(30, shift, 20);  
    178.             canvas.drawLine(shift, 20, shift, 30, tPaint);  
    179.             canvas.rotate(-60, shift, 20);  
    180.             canvas.drawLine(shift, 20, shift, 30, tPaint);  
    181.             canvas.rotate(30, shift, 20);  
    182.             canvas.rotate(30, sfvSurfaceView.getWidth()-1, baseLine);  
    183.             canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);  
    184.             canvas.rotate(-60, sfvSurfaceView.getWidth()-1, baseLine);  
    185.             canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);  
    186.             canvas.restore();  
    187.             //tPaint.setStyle(Style.STROKE);  
    188.             for(int index = 64; index <= 512; index = index + 64){  
    189.                 canvas.drawLine(shift + index, baseLine, shift + index, 40, dashPaint);  
    190.                 String str = String.valueOf(frequence / 1024 * index);  
    191.                 canvas.drawText( str, 0, str.length(), shift + index - 15, baseLine + 15, tPaint);  
    192.             }  
    193.             int y;  
    194.             for(int i = 0; i < buffer.length; i = i + 1){  
    195.                 y = baseLine - buffer[i] / rateY ;  
    196.                 canvas.drawLine(2*i + shift, baseLine, 2*i +shift, y, mPaint);  
    197.             }  
    198.             sfvSurfaceView.getHolder().unlockCanvasAndPost(canvas);  
    199.         }  
    200.     }  
    201.       
    202.     /** 
    203.      * 向上取最接近iint的2的幂次数.比如iint=320时,返回256 
    204.      * @param iint 
    205.      * @return 
    206.      */  
    207.     private int up2int(int iint) {  
    208.         int ret = 1;  
    209.         while (ret<=iint) {  
    210.             ret = ret << 1;  
    211.         }  
    212.         return ret>>1;  
    213.     }  
    214.       
    215.     //快速傅里叶变换  
    216.     public void fft(Complex[] xin,int N)  
    217.     {  
    218.         int f,m,N2,nm,i,k,j,L;//L:运算级数  
    219.         float p;  
    220.         int e2,le,B,ip;  
    221.         Complex w = new Complex();  
    222.         Complex t = new Complex();  
    223.         N2 = N / 2;//每一级中蝶形的个数,同时也代表m位二进制数最高位的十进制权值  
    224.         f = N;//f是为了求流程的级数而设立的  
    225.         for(m = 1; (f = f / 2) != 1; m++);                             //得到流程图的共几级  
    226.         nm = N - 2;  
    227.         j = N2;  
    228.         /******倒序运算——雷德算法******/  
    229.         for(i = 1; i <= nm; i++)  
    230.         {  
    231.             if(i < j)//防止重复交换  
    232.             {  
    233.                 t = xin[j];  
    234.                 xin[j] = xin[i];  
    235.                 xin[i] = t;  
    236.             }  
    237.             k = N2;  
    238.             while(j >= k)  
    239.             {  
    240.                 j = j - k;  
    241.                 k = k / 2;  
    242.             }  
    243.             j = j + k;  
    244.         }  
    245.         /******蝶形图计算部分******/  
    246.         for(L=1; L<=m; L++)                                    //从第1级到第m级  
    247.         {  
    248.             e2 = (int) Math.pow(2, L);  
    249.             //e2=(int)2.pow(L);  
    250.             le=e2+1;  
    251.             B=e2/2;  
    252.             for(j=0;j<B;j++)                                    //j从0到2^(L-1)-1  
    253.             {  
    254.                 p=2*pi/e2;  
    255.                 w.real = Math.cos(p * j);  
    256.                 //w.real=Math.cos((double)p*j);                                   //系数W  
    257.                 w.image = Math.sin(p*j) * -1;  
    258.                 //w.imag = -sin(p*j);  
    259.                 for(i=j;i<N;i=i+e2)                                //计算具有相同系数的数据  
    260.                 {  
    261.                     ip=i+B;                                           //对应蝶形的数据间隔为2^(L-1)  
    262.                     t=xin[ip].cc(w);  
    263.                     xin[ip] = xin[i].cut(t);  
    264.                     xin[i] = xin[i].sum(t);  
    265.                 }  
    266.             }  
    267.         }  
    268.     }  
    269. }  


            主程序

     

     

    [java] view plaincopy
     
    1. package com.mobao360.sunshine;  
    2.   
    3. import java.util.ArrayList;  
    4.   
    5. import android.app.Activity;  
    6. import android.app.AlertDialog;  
    7. import android.content.Context;  
    8. import android.content.DialogInterface;  
    9. import android.os.Bundle;  
    10. import android.util.Log;  
    11. import android.view.SurfaceView;  
    12. import android.view.View;  
    13. import android.widget.AdapterView;  
    14. import android.widget.ArrayAdapter;  
    15. import android.widget.Button;  
    16. import android.widget.Spinner;  
    17. import android.widget.TextView;  
    18. import android.widget.Toast;  
    19. import android.widget.ZoomControls;  
    20. import android.media.AudioFormat;  
    21. import android.media.AudioRecord;  
    22. import android.media.MediaRecorder;  
    23.   
    24. public class AudioMaker extends Activity {  
    25.     /** Called when the activity is first created. */  
    26.     static  int frequency = 8000;//分辨率    
    27.     static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;    
    28.     static final int audioEncodeing = AudioFormat.ENCODING_PCM_16BIT;   
    29.     static final int yMax = 50;//Y轴缩小比例最大值    
    30.     static final int yMin = 1;//Y轴缩小比例最小值    
    31.       
    32.     int minBufferSize;//采集数据需要的缓冲区大小  
    33.     AudioRecord audioRecord;//录音  
    34.     AudioProcess audioProcess = new AudioProcess();//处理  
    35.       
    36.     Button btnStart,btnExit;  //开始停止按钮  
    37.     SurfaceView sfv;  //绘图所用  
    38.     ZoomControls zctlX,zctlY;//频谱图缩放  
    39.     Spinner spinner;//下拉菜单  
    40.     ArrayList<String> list=new ArrayList<String>();  
    41.     ArrayAdapter<String>adapter;//下拉菜单适配器  
    42.     TextView tView;  
    43.       
    44.       
    45.     @Override  
    46.     public void onCreate(Bundle savedInstanceState) {  
    47.         super.onCreate(savedInstanceState);  
    48.         setContentView(R.layout.main);  
    49.           
    50.         initControl();  
    51.         }  
    52.     @Override  
    53.     protected void onDestroy(){  
    54.         super.onDestroy();  
    55.         android.os.Process.killProcess(android.os.Process.myPid());  
    56.     }  
    57.       
    58.   //初始化控件信息  
    59.     private void initControl() {  
    60.         //获取采样率  
    61.         tView = (TextView)this.findViewById(R.id.tvSpinner);  
    62.         spinner = (Spinner)this.findViewById(R.id.spinnerFre);  
    63.         String []ls =getResources().getStringArray(R.array.action);  
    64.         for(int i=0;i<ls.length;i++){  
    65.             list.add(ls[i]);  
    66.         }  
    67.         adapter=new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,list);  
    68.         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);  
    69.         spinner.setAdapter(adapter);  
    70.         spinner.setPrompt("请选择采样率");  
    71.         spinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){  
    72.             @SuppressWarnings("unchecked")  
    73.             public void onItemSelected(AdapterView arg0,View agr1,int arg2,long arg3){  
    74.                 frequency = Integer.parseInt(adapter.getItem(arg2));  
    75.                 tView.setText("您选择的是:"+adapter.getItem(arg2)+"HZ");  
    76.                 Log.i("sunshine",String.valueOf(minBufferSize));  
    77.                 arg0.setVisibility(View.VISIBLE);  
    78.             }  
    79.             @SuppressWarnings("unchecked")  
    80.             public void onNothingSelected(AdapterView arg0){  
    81.                 arg0.setVisibility(View.VISIBLE);  
    82.             }  
    83.         });  
    84.           
    85.           
    86.         Context mContext = getApplicationContext();  
    87.         //按键  
    88.         btnStart = (Button)this.findViewById(R.id.btnStart);  
    89.         btnExit = (Button)this.findViewById(R.id.btnExit);  
    90.         //按键事件处理  
    91.         btnStart.setOnClickListener(new ClickEvent());  
    92.         btnExit.setOnClickListener(new ClickEvent());  
    93.         //画笔和画板  
    94.         sfv = (SurfaceView)this.findViewById(R.id.SurfaceView01);  
    95.         //初始化显示  
    96.         audioProcess.initDraw(yMax/2, sfv.getHeight(),mContext,frequency);  
    97.         //画板缩放  
    98.         zctlY = (ZoomControls)this.findViewById(R.id.zctlY);  
    99.         zctlY.setOnZoomInClickListener(new View.OnClickListener() {    
    100.             @Override    
    101.             public void onClick(View v) {    
    102.                 if(audioProcess.rateY - 5>yMin){  
    103.                     audioProcess.rateY = audioProcess.rateY - 5;    
    104.                     setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");  
    105.                 }else{  
    106.                     audioProcess.rateY = 1;  
    107.                     setTitle("原始尺寸");  
    108.                 }  
    109.             }    
    110.         });    
    111.             
    112.         zctlY.setOnZoomOutClickListener(new View.OnClickListener() {    
    113.             @Override    
    114.             public void onClick(View v) {    
    115.                 if(audioProcess.rateY<yMax){  
    116.                     audioProcess.rateY = audioProcess.rateY + 5;        
    117.                     setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");    
    118.                 }else {  
    119.                     setTitle("Y轴已经不能再缩小");  
    120.                 }  
    121.             }    
    122.         });  
    123.     }  
    124.       
    125.     /** 
    126.      * 按键事件处理 
    127.      */  
    128.     class ClickEvent implements View.OnClickListener{  
    129.         @Override  
    130.         public void onClick(View v){  
    131.             Button button = (Button)v;  
    132.             if(button == btnStart){  
    133.                 if(button.getText().toString().equals("Start")){  
    134.                     try {  
    135.                         //录音  
    136.                         minBufferSize = AudioRecord.getMinBufferSize(frequency,   
    137.                                 channelConfiguration,   
    138.                                 audioEncodeing);  
    139.                         //minBufferSize = 2 * minBufferSize;   
    140.                         audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,frequency,  
    141.                                 channelConfiguration,  
    142.                                 audioEncodeing,  
    143.                                 minBufferSize);  
    144.                         audioProcess.baseLine = sfv.getHeight()-100;  
    145.                         audioProcess.frequence = frequency;  
    146.                         audioProcess.start(audioRecord, minBufferSize, sfv);  
    147.                         Toast.makeText(AudioMaker.this,   
    148.                                 "当前设备支持您所选择的采样率:"+String.valueOf(frequency),   
    149.                                 Toast.LENGTH_SHORT).show();  
    150.                         btnStart.setText(R.string.btn_exit);  
    151.                         spinner.setEnabled(false);  
    152.                     } catch (Exception e) {  
    153.                         // TODO: handle exception  
    154.                         Toast.makeText(AudioMaker.this,   
    155.                                 "当前设备不支持你所选择的采样率"+String.valueOf(frequency)+",请重新选择",   
    156.                                 Toast.LENGTH_SHORT).show();  
    157.                     }  
    158.                 }else if (button.getText().equals("Stop")) {  
    159.                     spinner.setEnabled(true);  
    160.                     btnStart.setText(R.string.btn_start);  
    161.                     audioProcess.stop(sfv);  
    162.                 }  
    163.             }  
    164.             else {  
    165.                 new AlertDialog.Builder(AudioMaker.this)   
    166.                  .setTitle("提示")   
    167.                  .setMessage("确定退出?")   
    168.                  .setPositiveButton("确定", new DialogInterface.OnClickListener() {   
    169.                 public void onClick(DialogInterface dialog, int whichButton) {   
    170.                 setResult(RESULT_OK);//确定按钮事件   
    171.                 AudioMaker.this.finish();  
    172.                  finish();   
    173.                  }   
    174.                  })   
    175.                  .setNegativeButton("取消", new DialogInterface.OnClickListener() {   
    176.                 public void onClick(DialogInterface dialog, int whichButton) {   
    177.                  //取消按钮事件   
    178.                  }   
    179.                  })   
    180.                  .show();  
    181.             }  
    182.               
    183.         }  
    184.     }  
    185. }  

     


     程序源码下载地址:http://download.csdn.net/detail/sunshine_okey/3790484

     

    详细的看代码吧,有什么写的详细的可以留言

    第一次写技术文章,写的不好,大家不要怪罪,将就着看把

  • 相关阅读:
    我是如何折腾.NET Resx资源文件的 当计算机中的资源已经足够多时,我们也要学会尽可能的借用
    当程序开发人员开始抛弃技术时,是否意味着噩梦的开始?抛弃了SQL Server 2000才发现客户的简单问题真的很难解决
    分享.NET ERP项目开发中应用到的重量级工具 选择合适的工具和资源,做项目效率高而且规范程度高
    Management Console ERP项目开发辅助工具 正确的方法+适当的工具使做项目的效率高而且问题少
    ERP系统管理员的工具箱 推荐几款优秀的数据比较同步工具 Data Compare and Sync tool
    亲自下载CSDN社区600万用户数据 设计两条编程题目考验你的.NET编程基础
    知识管理系统Data Solution研发日记之十六 保存服务器文档为本机PDF格式
    【转】好的学习方法
    iPhone开发学习笔记[7/50]在xcode里配置成功subversion
    iPhone开发学习笔记[4/50]表视图的使用
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4759949.html
Copyright © 2011-2022 走看看