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>

 

posted on 2014-09-02 17:00  宁静致远,一览众山小  阅读(796)  评论(0编辑  收藏  举报

导航