8.2.2 可视化频率

    人们通常用来分析音频的方法是可视化其中存在的频率。通常这些类型的可视化采用均衡器,均衡器允许调整各种频率范围的级别。

    将音频信号转换成分量频率(component frequency)的技术采用了一个数学变换,称为离散傅里叶变换(Discrete Fourier Transform,DFT)。DFT通常用来将基于时间的数据转换成基于频率的数据。一种用来执行DFT的算法是快速傅里叶变换(FFT),它非常有效,但是,非常复杂。

    幸运的是,存在许多FFT算法的实现,他们位于公共领域中,或者开放源代码,因此可以直接使用他们。其中的一个版本是FFTPACK库的java端口,这个库最初由国家大气研究中心的Paul Swarztrauber开发。其java端口由加拿大亚伯达省Lethbridge大学的Baoshe Zhang实现。www.netlib.org/fftpack/在线提供了各种实现。我们将要使用的文件称为jfftpack.tgz的存档,它由该页面提供链接。可以直接通过www.netlib,org/jffpack.tgz下载该文件。

    为了在一个Eclipse Android项目中使用这个程序包或任何其他包含java源代码的程序包,需要将源代码导入到项目中。由于这个存档包含了程序包的正确的目录结构,因此只要将位于javasource目录(ca)的顶层文件夹拖动到项目的src目录中。

    下面是一个示例,其绘制了图像均衡器的图形部分。

 1 package com.nthm.androidtestActivity;

 2 

 3 import com.nthm.androidtest.R;

 4 import android.app.Activity;

 5 import android.graphics.Bitmap;

 6 import android.graphics.Canvas;

 7 import android.graphics.Color;

 8 import android.graphics.Paint;

 9 import android.media.AudioFormat;

10 import android.media.AudioRecord;

11 import android.media.MediaRecorder;

12 import android.os.AsyncTask;

13 import android.os.Bundle;

14 import android.view.View;

15 import android.view.View.OnClickListener;

16 import android.widget.Button;

17 import android.widget.ImageView;

    我们将从fftpack包中导入RealDoubleFFT类。

1 import ca.uol.aig.fftpack.RealDoubleFFT;

2 public class AudioProcessing extends Activity implements OnClickListener {

    我们将在AudioRecord对象中使用8kHz的频率,单音频通道和16位的样本。

1     private int frequency=8000;

2     private int channelConfiguration=AudioFormat.CHANNEL_CONFIGURATION_MONO;

3     private int audioEncoding=AudioFormat.ENCODING_PCM_16BIT;

    transformer将是我们的FFT对象,通过该FFT对象,可以一次性处理来自AudioRecord对象的256个样本。使用的样本数量将对应于通过FFT对象运行他们之后获得的分量频率的数量。虽然可以自由选择不同的大小,但是确实需要考虑内存和性能的问题,因为需要计算的算术操作时处理器密集型的。

1     private RealDoubleFFT transformer;

2     private int blockSize=256;

3     private Button startStopButton;

4     private boolean started=false;

    下面定义的RecordAudio是内部类,其扩展了AsyncTask。

1     private RecordAudio recordTask;

    我们将使用ImageView显示一幅位图图像,其表示当前音频流中各种频率的级别。为了绘制这些级别,可以使用通过该位图构造的Canvas和Paint对象。

1     private ImageView imageView;

2     private Bitmap bitmap;

3     private Canvas canvas;

4     private Paint paint;

5     @Override

6     protected void onCreate(Bundle savedInstanceState) {

7         super.onCreate(savedInstanceState);

8         startStopButton=(Button) findViewById(R.id.StartStopButton);

9         startStopButton.setOnClickListener(this);

    RealDoubleFFT类的构造函数接受每次处理的样品数量作为参数。这也代表了将要输出的不同频率范围的数量。

1         transformer=new RealDoubleFFT(blockSize);

    下面是ImageView的设置和用于绘图的相关对象。

1         imageView=(ImageView) findViewById(R.id.ImageView01);

2         bitmap=Bitmap.createBitmap((int)256, (int)100, Bitmap.Config.ARGB_8888);

3         canvas=new Canvas(bitmap);

4         paint=new Paint();

5         paint.setColor(Color.GREEN);

6         imageView.setImageBitmap(bitmap);

7     }

    这个活动中的大部分工作在下面的RecordAudio类中完成,其扩展了AsyncTask。通过使用AsyncTask,可以在一个单独的线程上运行绑定用户界面的方法。任何放置在doInBackground方法中的代码都将以这种方式运行。

1     private class RecordAudio extends AsyncTask<Void, double[], Void>{

2 

3         @Override

4         protected Void doInBackground(Void... params) {

5             try{

    我们将以通常的方式建立和使用AudioRecord。

1                 int bufferSize=AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);

2                 AudioRecord audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);

    short类型的数组buffer将接受来自AudioRecord对象的原始PCM样本。Double类型的数组toTransform将以双精度的形式存储相同的数据,因为这是FFT类所需要的类型。

1                 short [] buffer=new short[blockSize];

2                 double[] toTransform=new double[blockSize];

3                 audioRecord.startRecording();

4                 while(started){

5                     int bufferReadResult=audioRecord.read(buffer, 0,blockSize);

    从AudioRecord对象中读取数据之后进行遍历,并将short值转换成double值。但是,不能直接通过强制类型转换这么做,因为期望值应该在-1.0~1.0之间,而不是整个值范围。将short值除以Short.MAX_VALUE将实现这个目的,因为该值是short类型的最大值。

1                     for(int i=0;i<blockSize&&i<bufferReadResult;i++){

2                         toTransform[i]=(double)buffer[i]/Short.MAX_VALUE;//有符号16位

3                     }

    接下来将双精度值的数组传递给FFT对象。FFT对象重用这个相同的数组来保存输出值。包含的数据将采用频域(frequency domain)而非时域(time domain),这意味着数组的第一个元素不是根据时间表示第一个样本——相反,它表示第一个频率集合的级别。

    由于使用256个值(或范围)且采样率是8000,因此可以确定数组中的每个元素将近似覆盖15.625Hz。通过将这个采样率除以2(因为可以捕获的最高频率是采样率的一半),然后除以256,就可以得到这个数字。因此,数组中第一个元素表示的数据将代表在0~15.625Hz之间的音频级别。

1                     transformer.ft(toTransform);

    调用publishProgress方法将会调用onProgressUpdate。

1                     publishProgress(toTransform);

2                 }

3                 audioRecord.stop();

4             }catch(Exception e){

5                 e.printStackTrace();

6             }

7             return null;

8         }

    onProgressUpdate子啊活动的主线程上运行,因此可以正常的与用户界面交互。在此实现中,传入了通过FFT对象运行之后的数据。该方法在屏幕上将数据绘制成一系列最大100像素高的线。每一条线表示数组中的一个元素,因此值范围是15.625Hz。第一条线表示频率范围是0~15.625Hz,而最后一条线表示频率范围是3984.375~4000Hz。

 1         @Override

 2         protected void onProgressUpdate(double[]... toTransform) {

 3             super.onProgressUpdate(toTransform);

 4             canvas.drawColor(Color.BLACK);

 5             for(int i=0;i<toTransform.length;i++){

 6                 int x=i;

 7                 int downy=(int)(100-(toTransform[0][i]*10));

 8                 int upy=100;

 9                 canvas.drawLine(x, downy, x, upy, paint);

10             }

11             imageView.invalidate();

12         }

13     }

14     @Override

15     public void onClick(View v) {

16        if(started){

17            started=false;

18            startStopButton.setText("Start");

19            recordTask.cancel(true);

20        }else{

21            started=true; 

22            startStopButton.setText("Stop");

23            recordTask=new RecordAudio();

24            recordTask.execute();

25        }

26     }

27 }

    下面是刚刚定义的AudioProcessing活动所使用的布局XML文件。

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

 2     android:layout_width="match_parent"

 3     android:layout_height="match_parent"

 4     android:orientation="vertical"

 5     >

 6  <TextView 

 7      android:id="@+id/StatusTextView"

 8      android:text="hello"

 9      android:layout_width="fill_parent"

10      android:layout_height="wrap_content"

11      android:textSize="35dip"></TextView>

12   <ImageView 

13       android:id="@+id/ImageView01"

14       android:layout_width="wrap_content"

15       android:layout_height="wrap_content"

16       />

17  <Button 

18      android:layout_width="wrap_content"

19      android:layout_height="wrap_content"

20      android:id="@+id/StartStopButton"

21      android:text="Start"/>

22 

23 </LinearLayout>

 

你可能感兴趣的:(可视化)